Java tutorial
/** * 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.apache.aurora.common.args.apt; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.io.CharSource; import com.google.common.io.CharStreams; import com.google.common.io.LineProcessor; import com.google.common.io.Resources; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; /** * Loads and stores {@literal @CmdLine} configuration data. By default, that data * is contained in text files called cmdline.arg.info.txt.0, cmdline.arg.info.txt.1 * etc. Every time a new Configuration object is created, it consumes all existing * files with the above names. Saving this Configuration results in creation of a * file with index increased by one, e.g. cmdline.arg.info.txt.2 in the above * example. * * @author John Sirois */ public final class Configuration { /** * Indicates a problem reading stored {@literal @CmdLine} arg configuration data. */ public static class ConfigurationException extends RuntimeException { public ConfigurationException(String message, Object... args) { super(String.format(message, args)); } public ConfigurationException(Throwable cause) { super(cause); } } static final String DEFAULT_RESOURCE_PACKAGE = Configuration.class.getPackage().getName(); private static final Logger LOG = Logger.getLogger(Configuration.class.getName()); private static final CharMatcher IDENTIFIER_START = CharMatcher.forPredicate(Character::isJavaIdentifierStart); private static final CharMatcher IDENTIFIER_REST = CharMatcher.forPredicate(Character::isJavaIdentifierPart); private static final Function<URL, CharSource> URL_TO_SOURCE = input -> Resources.asCharSource(input, StandardCharsets.UTF_8); private static final String DEFAULT_RESOURCE_NAME = "cmdline.arg.info.txt"; private int nextResourceIndex; private final ImmutableSet<ArgInfo> cmdLineInfos; private final ImmutableSet<ParserInfo> parserInfos; private final ImmutableSet<VerifierInfo> verifierInfos; private Configuration(int nextResourceIndex, Iterable<ArgInfo> cmdLineInfos, Iterable<ParserInfo> parserInfos, Iterable<VerifierInfo> verifierInfos) { this.nextResourceIndex = nextResourceIndex; this.cmdLineInfos = ImmutableSet.copyOf(cmdLineInfos); this.parserInfos = ImmutableSet.copyOf(parserInfos); this.verifierInfos = ImmutableSet.copyOf(verifierInfos); } private static String checkValidIdentifier(String identifier, boolean compound) { Preconditions.checkNotNull(identifier); String trimmed = identifier.trim(); Preconditions.checkArgument(!trimmed.isEmpty(), "Invalid identifier: '%s'", identifier); String[] parts = compound ? trimmed.split("\\.") : new String[] { trimmed }; for (String part : parts) { Preconditions.checkArgument(IDENTIFIER_REST.matchesAllOf(IDENTIFIER_START.trimLeadingFrom(part)), "Invalid identifier: '%s'", identifier); } return trimmed; } public static final class ArgInfo { public final String className; public final String fieldName; public ArgInfo(String className, String fieldName) { this.className = checkValidIdentifier(className, true); this.fieldName = checkValidIdentifier(fieldName, false); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ArgInfo)) { return false; } ArgInfo other = (ArgInfo) obj; return new EqualsBuilder().append(className, other.className).append(fieldName, other.fieldName) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder().append(className).append(fieldName).toHashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("className", className).append("fieldName", fieldName) .toString(); } } public static final class ParserInfo { public final String parsedType; public final String parserClass; public ParserInfo(String parsedType, String parserClass) { this.parsedType = checkValidIdentifier(parsedType, true); this.parserClass = checkValidIdentifier(parserClass, true); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof ParserInfo)) { return false; } ParserInfo other = (ParserInfo) obj; return new EqualsBuilder().append(parsedType, other.parsedType).append(parserClass, other.parserClass) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder().append(parsedType).append(parserClass).toHashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("parsedType", parsedType).append("parserClass", parserClass) .toString(); } } public static final class VerifierInfo { public final String verifiedType; public final String verifyingAnnotation; public final String verifierClass; public VerifierInfo(String verifiedType, String verifyingAnnotation, String verifierClass) { this.verifiedType = checkValidIdentifier(verifiedType, true); this.verifyingAnnotation = checkValidIdentifier(verifyingAnnotation, true); this.verifierClass = checkValidIdentifier(verifierClass, true); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof VerifierInfo)) { return false; } VerifierInfo other = (VerifierInfo) obj; return new EqualsBuilder().append(verifiedType, other.verifiedType) .append(verifyingAnnotation, other.verifyingAnnotation) .append(verifierClass, other.verifierClass).isEquals(); } @Override public int hashCode() { return new HashCodeBuilder().append(verifiedType).append(verifyingAnnotation).append(verifierClass) .toHashCode(); } @Override public String toString() { return new ToStringBuilder(this).append("verifiedType", verifiedType) .append("verifyingAnnotation", verifyingAnnotation).append("verifierClass", verifierClass) .toString(); } } static class Builder { private final Set<ArgInfo> argInfos = Sets.newHashSet(); private final Set<ParserInfo> parserInfos = Sets.newHashSet(); private final Set<VerifierInfo> verifierInfos = Sets.newHashSet(); public boolean isEmpty() { return argInfos.isEmpty() && parserInfos.isEmpty() && verifierInfos.isEmpty(); } void addCmdLineArg(ArgInfo argInfo) { argInfos.add(argInfo); } void addParser(ParserInfo parserInfo) { parserInfos.add(parserInfo); } public void addParser(String parserForType, String parserType) { addParser(new ParserInfo(parserForType, parserType)); } void addVerifier(VerifierInfo verifierInfo) { verifierInfos.add(verifierInfo); } public void addVerifier(String verifierForType, String annotationType, String verifierType) { addVerifier(new VerifierInfo(verifierForType, annotationType, verifierType)); } public Configuration build(Configuration configuration) { return new Configuration(configuration.nextResourceIndex + 1, argInfos, parserInfos, verifierInfos); } } private static String getResourceName(int index) { return String.format("%s.%s", DEFAULT_RESOURCE_NAME, index); } private static String getResourcePath(int index) { return String.format("%s/%s", DEFAULT_RESOURCE_PACKAGE.replace('.', '/'), getResourceName(index)); } static final class ConfigurationResources { private final int nextResourceIndex; private final Iterator<URL> resources; private ConfigurationResources(int nextResourceIndex, Iterator<URL> resources) { this.nextResourceIndex = nextResourceIndex; this.resources = resources; } } /** * Loads the {@literal @CmdLine} argument configuration data stored in the classpath. * * @return The {@literal @CmdLine} argument configuration materialized from the classpath. * @throws ConfigurationException if any configuration data is malformed. * @throws IOException if the configuration data can not be read from the classpath. */ public static Configuration load() throws ConfigurationException, IOException { ConfigurationResources allResources = getAllResources(); List<URL> configs = ImmutableList.copyOf(allResources.resources); if (configs.isEmpty()) { LOG.info("No @CmdLine arg configs found on the classpath"); } else { LOG.info("Loading @CmdLine config from: " + configs); } return load(allResources.nextResourceIndex, configs); } private static ConfigurationResources getAllResources() throws IOException { int maxResourceIndex = 0; Iterator<URL> allResources = getResources(0); // Try for a main // Probe for resource files with index up to 10 (or more, while resources at the // given index can be found) for (int nextResourceIndex = 1; nextResourceIndex <= maxResourceIndex + 10; nextResourceIndex++) { Iterator<URL> resources = getResources(nextResourceIndex); if (resources.hasNext()) { allResources = Iterators.concat(allResources, resources); maxResourceIndex = nextResourceIndex; } } return new ConfigurationResources(maxResourceIndex + 1, allResources); } private static Iterator<URL> getResources(int index) throws IOException { return Iterators.forEnumeration(Configuration.class.getClassLoader().getResources(getResourcePath(index))); } private static final class ConfigurationParser implements LineProcessor<Configuration> { private final int nextIndex; private int lineNumber = 0; private final ImmutableList.Builder<ArgInfo> fieldInfoBuilder = ImmutableList.builder(); private final ImmutableList.Builder<ParserInfo> parserInfoBuilder = ImmutableList.builder(); private final ImmutableList.Builder<VerifierInfo> verifierInfoBuilder = ImmutableList.builder(); private ConfigurationParser(int nextIndex) { this.nextIndex = nextIndex; } @Override public boolean processLine(String line) throws IOException { ++lineNumber; String trimmed = line.trim(); if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { List<String> parts = Lists.newArrayList(trimmed.split(" ")); if (parts.size() < 1) { throw new ConfigurationException("Invalid line: %s @%d", trimmed, lineNumber); } String type = parts.remove(0); if ("field".equals(type)) { if (parts.size() != 2) { throw new ConfigurationException("Invalid field line: %s @%d", trimmed, lineNumber); } fieldInfoBuilder.add(new ArgInfo(parts.get(0), parts.get(1))); } else if ("parser".equals(type)) { if (parts.size() != 2) { throw new ConfigurationException("Invalid parser line: %s @%d", trimmed, lineNumber); } parserInfoBuilder.add(new ParserInfo(parts.get(0), parts.get(1))); } else if ("verifier".equals(type)) { if (parts.size() != 3) { throw new ConfigurationException("Invalid verifier line: %s @%d", trimmed, lineNumber); } verifierInfoBuilder.add(new VerifierInfo(parts.get(0), parts.get(1), parts.get(2))); } else { LOG.warning(String.format("Did not recognize entry type %s for line: %s @%d", type, trimmed, lineNumber)); } } return true; } @Override public Configuration getResult() { return new Configuration(nextIndex, fieldInfoBuilder.build(), parserInfoBuilder.build(), verifierInfoBuilder.build()); } } private static Configuration load(int nextIndex, List<URL> configs) throws ConfigurationException, IOException { CharSource input = CharSource.concat(Iterables.transform(configs, URL_TO_SOURCE)); try (Reader reader = input.openStream()) { return CharStreams.readLines(reader, new ConfigurationParser(nextIndex)); } } public boolean isEmpty() { return cmdLineInfos.isEmpty() && parserInfos.isEmpty() && verifierInfos.isEmpty(); } /** * Returns the field info for all the {@literal @CmdLine} annotated fields on the classpath. * * @return The field info for all the {@literal @CmdLine} annotated fields. */ public Iterable<ArgInfo> optionInfo() { return cmdLineInfos; } /** * Returns the parser info for all the {@literal @ArgParser} annotated parsers on the classpath. * * @return The parser info for all the {@literal @ArgParser} annotated parsers. */ public Iterable<ParserInfo> parserInfo() { return parserInfos; } /** * Returns the verifier info for all the {@literal @VerifierFor} annotated verifiers on the * classpath. * * @return The verifier info for all the {@literal @VerifierFor} annotated verifiers. */ public Iterable<VerifierInfo> verifierInfo() { return verifierInfos; } static String mainResourceName() { return getResourceName(0); } String nextResourceName() { return getResourceName(nextResourceIndex); } void store(Writer output, String message) { PrintWriter writer = new PrintWriter(output); writer.printf("# %s\n", new Date()); writer.printf("# %s\n ", message); writer.println(); for (ArgInfo info : cmdLineInfos) { writer.printf("field %s %s\n", info.className, info.fieldName); } writer.println(); for (ParserInfo info : parserInfos) { writer.printf("parser %s %s\n", info.parsedType, info.parserClass); } writer.println(); for (VerifierInfo info : verifierInfos) { writer.printf("verifier %s %s %s\n", info.verifiedType, info.verifyingAnnotation, info.verifierClass); } } }