All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yarnandtail.andhow.compile.CompileUnit Maven / Gradle / Ivy

Go to download

Compile-time annotation processor that records all AndHow Properties and registers them as service providers to be picked up at runtime.

There is a newer version: 1.5.0
Show newest version
package org.yarnandtail.andhow.compile;

import org.yarnandtail.andhow.service.PropertyRegistrationList;
import org.yarnandtail.andhow.util.NameUtil;
import java.util.*;

/**
 * Incrementally built metadata about a Type (compile-time representation of a class)
 * and its AndHow Properties (if any) as it is compiled.
 *
 * One instance of a CompileUnit corresponds to a single top level class, that is,
 * a class or interface that is not an inner class and for which the only logical
 * parent is its package. Inner classes and interfaces are represented as the
 * 'inner path' - the heirarchical list of nested inner classes from the outer
 * to the innermost one.  The state of the inner path is recorded each time
 * addProperty() is called, so that each Property can be registered with the
 * appropriate inner path, which will then make up its canonical name.
 *
 * This class is used by doing a depth first scan of a Type. As each Type is
 * found it is pushType'ed into the CompileUnit.  Types are popType()'ed  out
 * when the scan leaves a type.  Within a type, variables are scanned. If a
 * variable with the type AndHow Property is discovered and the assignment is
 * from a newly constructed Property (not just a reference), addProperty() is
 * called and the metadata about the inner path and variable name are recorded
 * here.
 * 
 * This class is stateful and is not thread safe.
 *
 * @author ericeverman
 */
public class CompileUnit {

	private final String classCanonName;
	private PropertyRegistrationList registrations;	//late init
	private List errors;	//late init
	private boolean initClass;	//True if an AndHowInit instance (and not AndHowTestInit)
	private boolean testInitClass;	//True if an AndHowTestInit instance
	
	/**
	 * This is being used as a stack. Always push into the tail of the queue and
	 * always 'pop' from the tail. The nested inner class order from outer to
	 * inner is then the normal iteration order of the queue from the head to
	 * the tail.  Uses late initiation.
	 */
	private ArrayDeque innerPathStack = new ArrayDeque();

	/**
	 * Construct a new CompileUnit, which always is for a specific top level
	 * class.
	 *
	 * A top level class is a non-inner class.
	 *
	 * @param classCanonName The fully qualified name of a top level class.
	 */
	public CompileUnit(String classCanonName) {
		this.classCanonName = classCanonName;
	}
	
	public boolean isInitClass() {
		return initClass;
	}
	
	public void setInitClass(boolean initClass) {
		this.initClass = initClass;
	}
	
	public boolean istestInitClass() {
		return testInitClass;
	}
	
	public void setTestInitClass(boolean testInitClass) {
		this.testInitClass = testInitClass;
	}

	public void pushType(SimpleType simpleName) {
		
		if (innerPathStack == null) {
			innerPathStack = new ArrayDeque();
		}
		
		innerPathStack.addLast(simpleName);
	}

	public void pushType(String name, boolean _static) {
		pushType(new SimpleType(name, _static));
	}

	public SimpleType popType() {
		
		if (innerPathStack == null || innerPathStack.size() == 0) {
			throw new RuntimeException("The nesting order of inner classes is broken - expected to be in an inner class.");
		}
		return innerPathStack.pollLast();
	}

	/**
	 * Register an AndHow Property declaration in the current scope - either
	 * directly in the the top level class or the recorded path to an inner
	 * class.
	 *
	 * If modifiers are invalid, an error will be recorded rather than a
	 * Property.
	 *
	 * @param variableElement A SimpleType representing a variable to which an
	 * AndHow property is constructed and assigned to.
	 * @return True if the property could be added, false if an error was
	 * recorded instead.
	 */
	public boolean addProperty(SimpleVariable variableElement) {

		if (variableElement.isStatic() && variableElement.isFinal()) {
			if (registrations == null) {
				registrations = new PropertyRegistrationList(classCanonName);
			}

			registrations.add(variableElement.getName(), getInnerPathNames());

			return true;
		} else {
			addPropertyError(variableElement.getName(), "New AndHow Properties must be assigned to a static final field.");
			return false;
		}
	}

	/**
	 * Register an AndHow Property declaration in the current scope - either
	 * directly in the the top level class or the recorded path to an inner
	 * class.
	 *
	 * If modifiers are invalid, an error will be recorded rather than a
	 * Property.
	 *
	 * @param name The name of the variable the Property is assigned to.
	 * @param _static Does the variable has the static modifier?
	 * @param _final Is the variable declared as static?
	 * @return True if the property could be added, false if an error was
	 * recorded instead.
	 */
	public boolean addProperty(String name, boolean _static, boolean _final) {
		return addProperty(new SimpleVariable(name, _static, _final));
	}

	/**
	 * Return the state of inner class nesting from the outermost to the
	 * innermost.
	 *
	 * If the current state is at the root of the top level class, an empty list
	 * is returned.
	 *
	 * @see getInnerPathNames() for just the names of the nested inner classes.
	 * @return
	 */
	public List getInnerPath() {

		List innerPath;

		if (innerPathStack != null && innerPathStack.size() > 0) {
			innerPath = new ArrayList(innerPathStack);
			//Collections.reverse(innerPath);
		} else {
			innerPath = Collections.EMPTY_LIST;
		}

		return innerPath;
	}

	/**
	 * Return the inner class names, in order from the outermost to the
	 * innermost.
	 *
	 * If the current state is at the root of the top level class, an empty list
	 * is returned.
	 *
	 * @return
	 */
	public List getInnerPathNames() {

		List pathNames;

		if (innerPathStack != null && innerPathStack.size() > 0) {
			pathNames = new ArrayList();

			Iterator it = innerPathStack.iterator();

			while (it.hasNext()) {
				pathNames.add(it.next().getName());
			}

		} else {
			pathNames = Collections.EMPTY_LIST;
		}

		return pathNames;
	}

	public void addPropertyError(String propName, String msg) {

		if (errors == null) {
			errors = new ArrayList();
		}

		String parentName = NameUtil.getJavaName(classCanonName, this.getInnerPathNames());
		
		errors.add("The AndHow Property '" + propName + "' in " + parentName + " is invalid: " + msg);
	}

	/**
	 * The fully qualified name of the top level class this CompileUnit is for.
	 *
	 * @return
	 */
	public String getRootCanonicalName() {
		return classCanonName;
	}
	
	/**
	 * Get just the simple name of the root class.
	 * 
	 * @return 
	 */
	public String getRootSimpleName() {
		
		int dotPos = classCanonName.lastIndexOf(".");
		
		if (dotPos > 0) {
			return classCanonName.substring(dotPos + 1);
		} else {
			return classCanonName;
		}
		
	}
	
	/**
	 * Get just the package of the root class.
	 * 
	 * @return null if the root class is in the default package.
	 */
	public String getRootPackageName() {
		
		int dotPos = classCanonName.lastIndexOf(".");
		
		if (dotPos > 0) {
			return classCanonName.substring(0, dotPos);
		} else {
			return null;
		}
		
	}

	/**
	 * The list of Properties in the CompileUnit, along with all the needed
	 * metadata to register them.
	 *
	 * @return
	 */
	public PropertyRegistrationList getRegistrations() {
		return registrations;
	}

	/**
	 * The list of Property errors, either directly added or created indirectly
	 * by adding properties that had invalid modifiers.
	 *
	 * @return
	 */
	public List getErrors() {
		if (errors != null) {
			return errors;
		} else {
			return Collections.EMPTY_LIST;
		}
	}

	/**
	 * Returns true if the getErrors() list would be non-empty.
	 *
	 * @return
	 */
	public boolean hasErrors() {
		if (errors != null) {
			return !errors.isEmpty();
		} else {
			return false;
		}
		
	}

	/**
	 * Returns true if getRegistrations() would return a non-empty list.
	 *
	 * @return
	 */
	public boolean hasRegistrations() {
		return registrations != null && !registrations.isEmpty();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy