/*
* Copyright 2015 Google Inc. All rights reserved.
*
* 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 org.inferred.freebuilder.processor.util.feature;
import com.google.common.base.Optional;
import org.inferred.freebuilder.processor.util.Excerpt;
import org.inferred.freebuilder.processor.util.QualifiedName;
import org.inferred.freebuilder.processor.util.Shading;
import org.inferred.freebuilder.processor.util.SourceBuilder;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.util.Elements;
/**
* Compliance levels which are idiomatically supported by this processor.
*
* <p>{@link SourceVersion} is problematic to use, as the constants themselves will be missing
* on compilers that do not support them (e.g. "RELEASE_8" is not available on javac v6 or v7).
* Additionally, {@code sourceLevel.javaUtilObjects().isPresent()} is far more readable than
* {@code sourceVersion.compareTo(SourceLevel.RELEASE_7) >= 0}.
*/
public enum SourceLevel implements Feature<SourceLevel> {
JAVA_6("Java 6"), JAVA_7("Java 7"), JAVA_8("Java 8+");
/**
* Constant to pass to {@link SourceBuilder#feature(FeatureType)} to get the current
* {@link SourceLevel}.
*/
public static final FeatureType<SourceLevel> SOURCE_LEVEL = new FeatureType<SourceLevel>() {
@Override
protected SourceLevel testDefault(FeatureSet features) {
return JAVA_6;
}
@Override
protected SourceLevel forEnvironment(ProcessingEnvironment env, FeatureSet features) {
int sourceVersion = env.getSourceVersion().ordinal();
if (sourceVersion <= 6) {
// RELEASE_6 is always available, as previous releases did not support annotation processing
return JAVA_6;
} else if (sourceVersion >= 8) {
// Return JAVA_8 for versions 9+ also.
return JAVA_8;
} else if (runningInEclipse()) {
// Some versions of Eclipse erroneously return RELEASE_7 instead of RELEASE_8.
// Work around this by checking for the presence of java.util.Stream instead.
return hasType(env.getElementUtils(), STREAM) ? JAVA_8 : JAVA_7;
} else {
return JAVA_7;
}
}
};
private static final QualifiedName STREAM = QualifiedName.of("java.util.stream", "Stream");
private static final String ECLIPSE_DISPATCHER =
Shading.unshadedName("org.eclipse.jdt.internal.compiler.apt.dispatch.RoundDispatcher");
public static Excerpt diamondOperator(final Object type) {
return new DiamondOperator(type);
}
private static final class DiamondOperator extends Excerpt {
private final Object type;
private DiamondOperator(Object type) {
this.type = type;
}
@Override
public void addTo(SourceBuilder source) {
if (source.feature(SOURCE_LEVEL).compareTo(JAVA_7) >= 0) {
source.add("<>");
} else {
source.add("<%s>", type);
}
}
@Override
public String toString() {
return "diamondOperator(" + type + ")";
}
@Override
protected void addFields(FieldReceiver fields) {
fields.add("type", type);
}
}
private final String humanReadableFormat;
SourceLevel(String humanReadableFormat) {
this.humanReadableFormat = humanReadableFormat;
}
public Optional<QualifiedName> javaUtilObjects() {
switch (this) {
case JAVA_6:
return Optional.absent();
default:
return Optional.of(QualifiedName.of("java.util", "Objects"));
}
}
public boolean hasLambdas() {
return compareTo(JAVA_8) >= 0;
}
public Optional<QualifiedName> baseStream() {
switch (this) {
case JAVA_6:
case JAVA_7:
return Optional.absent();
default:
return Optional.of(QualifiedName.of("java.util.stream", "BaseStream"));
}
}
public Optional<QualifiedName> stream() {
switch (this) {
case JAVA_6:
case JAVA_7:
return Optional.absent();
default:
return Optional.of(STREAM);
}
}
public Optional<QualifiedName> spliterator() {
switch (this) {
case JAVA_6:
case JAVA_7:
return Optional.absent();
default:
return Optional.of(QualifiedName.of("java.util", "Spliterator"));
}
}
@Override
public String toString() {
return humanReadableFormat;
}
private static boolean hasType(Elements elements, QualifiedName type) {
return elements.getTypeElement(type.toString()) != null;
}
private static boolean runningInEclipse() {
// If we're running in Eclipse, we will have been invoked by the Eclipse round dispatcher.
Throwable t = new Throwable();
t.fillInStackTrace();
for (StackTraceElement method : t.getStackTrace()) {
if (method.getClassName().equals(ECLIPSE_DISPATCHER)) {
return true;
} else if (!method.getClassName().startsWith("org.inferred")) {
return false;
}
}
return false;
}
}