Skip to content

Commit

Permalink
An initial import of @autovalue
Browse files Browse the repository at this point in the history
  • Loading branch information
gk5885 committed Aug 23, 2013
1 parent fa7058a commit d67532c
Show file tree
Hide file tree
Showing 10 changed files with 1,973 additions and 0 deletions.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<module>compile-testing</module>
<module>factory</module>
<module>service</module>
<module>value</module>
</modules>

<properties>
Expand Down
81 changes: 81 additions & 0 deletions value/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2012 Google, Inc. Copyright (C) 2012 Square, Inc. Licensed
under the Apache License, Version 2.0 (the "License"); you may not use this
file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.auto</groupId>
<artifactId>auto-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<name>Auto-Value</name>
<description>
The Auto-Value code generator.
</description>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>1.3.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.truth0</groupId>
<artifactId>truth</artifactId>
<version>0.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>14.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
</project>
130 changes: 130 additions & 0 deletions value/src/main/java/com/google/auto/value/AbstractMethodExtractor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2013 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.value;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* An ultrasimplified Java parser for {@link EclipseHack} that examines classes to extract just
* the abstract methods. The parsing is very superficial. It assumes that the source text is
* syntactically correct, which it must be in the context of an annotation processor because the
* compiler doesn't invoke the processor if there are syntax errors.
*
* <p>We recognize the text {@code ... class Foo ... { ... } } as a class called Foo, whose
* definition extends to the matching right brace. Within a class definition, we recognize the text
* {@code abstract ... bar ( ) } as an abstract method called bar.
*
* <p>We construct a {@code Map<String, List<String>>} that represents the abstract methods found in
* each class, in the order they were found. If com.example.Foo contains a nested class Bar, then
* there will be an entry for "com.example.Foo.Bar" in this Map.
*
* @author [email protected] (Éamonn McManus)
*/
final class AbstractMethodExtractor {
AbstractMethodExtractor() {}

// Here are the details of the matching. We track the current brace depth, and we artificially
// consider that the whole file is at brace depth 1 inside a pseudo-class whose name is the
// name of the package. When we see a class definition, we push the fully-qualified name of the
// class on a stack so that we can associate abstract methods we find with the possibly-nested
// class they belong to. A class definition must occur at brace depth one more than the class
// containing it, which is equivalent to saying that the brace depth must be the same as the
// class stack depth. This check excludes local class definitions within methods and
// initializers. If we meet these constraints and we see the word "class" followed by an
// identifier Foo, then we consider that we are entering the definition of class Foo. We determine
// the fully-qualified name of Foo, which is container.Foo, where container is the current top of
// the class stack (initially, the package name). We push this new fully-qualified name on the
// class stack. We have not yet seen the left brace with the class definition so at this point the
// class stack depth is one more than the brace depth. When we subsequently see a right brace that
// takes us back to this situation then we know we have completed the definition of Foo and we can
// pop it from the class stack.
//
// We check that the token after "class" is indeed an identifier to avoid confusion
// with Foo.class. Even though the tokenizer does not distinguish between identifiers and
// keywords, it is enough to exclude the single word "instanceof" because that is the only word
// that can legally appear after Foo.class (though in a legal program the resultant expression
// will always be true).
//
// Again, we are at the top level of a class when the brace depth is equal to the class stack
// depth. If we then see the word "abstract" then that is the start either of an abstract class
// definition or of an abstract method. We record that we have seen "abstract" and we cancel
// that indication as soon as we see a left brace, to exclude the abstract class case, and also
// the case of interfaces or @interfaces redundantly declared abstract. Now, when
// we see an identifier that is preceded by an uncanceled "abstract" and followed by a left paren
// then we have found an abstract method of the class on the top of the class stack. We record it
// in the list of abstract methods of the class on the top of the class stack. We don't bother
// checking that the method has no parameters, because an @AutoValue class will cause a compiler
// error if there are abstract methods with parameters, since the @AutoValue processor doesn't
// know how to implement them in the concrete subclass it generates.
Map<String, List<String>> abstractMethods(JavaTokenizer tokenizer, String packageName) {
Map<String, List<String>> abstractMethods = new HashMap<String, List<String>>();
Deque<String> classStack = new ArrayDeque<String>();
classStack.addLast(packageName);
int braceDepth = 1;
boolean sawAbstract = false;
String className = null;
for (String previousToken = "", token = tokenizer.nextToken();
token != null;
previousToken = token, token = tokenizer.nextToken()) {
boolean topLevel = (braceDepth == classStack.size());
if (className != null) {
// get last term in fully-qualified class name (e.g. "class some.package.Bar { ...")
if (token.equals(".")) {
className = tokenizer.nextToken();
continue;
} else {
if (Character.isJavaIdentifierStart(className.charAt(0))
&& !className.equals("instanceof")) {
String container = classStack.getLast();
classStack.addLast(container + "." + className);
}
className = null;
}
}
if (token.equals("{")) {
braceDepth++;
sawAbstract = false;
} else if (token.equals("}")) {
braceDepth--;
if (topLevel) {
classStack.removeLast();
}
} else if (topLevel) {
if (token.equals("class")) {
className = tokenizer.nextToken();
} else if (token.equals("abstract")) {
sawAbstract = true;
} else if (token.equals("(")) {
if (sawAbstract && Character.isJavaIdentifierStart(previousToken.charAt(0))) {
List<String> methods = abstractMethods.get(classStack.getLast());
if (methods == null) {
methods = new ArrayList<String>();
abstractMethods.put(classStack.getLast(), methods);
}
methods.add(previousToken);
}
sawAbstract = false;
}
}
}
return abstractMethods;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2013 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.value;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
* A class file parser that lists the no-arg abstract methods in a class.
*
* @author Éamonn McManus
*/
class AbstractMethodLister {
private final InputStream inputStream;

AbstractMethodLister(InputStream inputStream) {
this.inputStream = inputStream;
}

List<String> abstractNoArgMethods() {
try {
return abstractNoArgMethodsX();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private List<String> abstractNoArgMethodsX() throws IOException {
ClassReader classReader = new ClassReader(inputStream);
RecordingClassVisitor classVisitor = new RecordingClassVisitor();
classReader.accept(classVisitor, 0);
return classVisitor.abstractNoArgMethods;
}

private static class RecordingClassVisitor extends ClassVisitor {
private final List<String> abstractNoArgMethods = new ArrayList<String>();

RecordingClassVisitor() {
super(Opcodes.ASM4);
}

@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
// The class-file method descriptor desc is a string that will contain "()" only if the
// method has no arguments, and will end with "V" (actually "()V") only if the method
// is void.
if (Modifier.isAbstract(access) && desc.contains("()") && !desc.endsWith("V")) {
abstractNoArgMethods.add(name);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
}
72 changes: 72 additions & 0 deletions value/src/main/java/com/google/auto/value/AutoValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2012 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.auto.value;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation indicating that the annotated type is a value type with an automatically-generated
* implementation.
*
* <p>Here is a simple example of a value type using {@code @AutoValue}:
*
* <pre>
* {@code @AutoValue}
* {@code public abstract class Contact {
* public abstract String name();
* public abstract List<String> phoneNumbers();
* public abstract int sortOrder();
*
* public static Contact create(String name, List<String> phoneNumbers, int sortOrder) {
* return new AutoValue_Contact(name, phoneNumbers, sortOrder);
* }
* }}</pre>
*
* <p>The generated subclass is called {@code AutoValue_Contact} but users of {@code Contact} do not
* need to know that. Only the implementation of the {@code create} method does.
*
* <p>As an alternative to instantiating the generated subclass in the {@code create} method, you
* can define a {@code Factory} interface like this:</p>
*
* <pre>
* {@code @AutoValue}
* {@code public abstract class Contact {
* public abstract String name();
* public abstract List<String> phoneNumbers();
* public abstract int sortOrder();
*
* public static Contact create(String name, List<String> phoneNumbers, int sortOrder) {
* return AutoValues.using(Factory.class).create(name, phoneNumbers, sortOrder);
* }
*
* interface Factory {
* Contact create(String name, List<String> phoneNumbers, int sortOrder);
* }
* }}</pre>
*
* @author Éamonn McManus
*/
/*
* TODO(gak): We should obviously try to figure out whether this whole factory mechanism can be
* replaced with @AutoFactory
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoValue {
}
Loading

0 comments on commit d67532c

Please sign in to comment.