/*
* Copyright (c) 2001-2005 Ant-Contrib project. 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 net.sf.antcontrib.design;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import net.sf.antcontrib.logic.ProjectDelegate;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
/*
* Created on Aug 24, 2003
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
/**
* FILL IN JAVADOC HERE
*
* @author Dean Hiller(dean@xsoftware.biz)
*/
public class Design {
private Map nameToPackage = new HashMap();
private Map packageNameToPackage = new HashMap();
private boolean isCircularDesign;
private Log log;
private Location location;
private String currentClass = null;
private String currentPackageName = null;
private Package currentAliasPackage = null;
private HashSet primitives = new HashSet();
public Design(boolean isCircularDesign, Log log, Location loc) {
//by default, add java as a configured package with the name java
Package p = new Package();
p.setIncludeSubpackages(true);
p.setName("java");
p.setUsed(true);
p.setNeedDeclarations(false);
p.setPackage("java");
addConfiguredPackage(p);
this.isCircularDesign = isCircularDesign;
this.log = log;
this.location = loc;
primitives.add("boolean");
//integral types
primitives.add("byte");
primitives.add("short");
primitives.add("int");
primitives.add("long");
primitives.add("char");
//floating point types
primitives.add("double");
primitives.add("float");
}
public Package getPackage(String nameAttribute) {
return (Package)nameToPackage.get(nameAttribute);
}
private Package retreivePack(String thePackage) {
if(thePackage == null)
throw new IllegalArgumentException("Cannot retrieve null packages");
String currentPackage = thePackage;
Package result = (Package)packageNameToPackage.get(currentPackage);
while(!Package.DEFAULT.equals(currentPackage)) {
log.log("p="+currentPackage+"result="+result, Project.MSG_DEBUG);
if(result != null) {
if(currentPackage.equals(thePackage))
return result;
else if(result.isIncludeSubpackages())
return result;
return null;
}
currentPackage = VerifyDesignDelegate.getPackageName(currentPackage);
result = (Package)packageNameToPackage.get(currentPackage);
}
//result must now be default package
if(result != null && result.isIncludeSubpackages())
return result;
return null;
}
public void addConfiguredPackage(Package p) {
String pack = p.getPackage();
Depends[] depends = p.getDepends();
if(depends != null && !isCircularDesign) {
//make sure all depends are in Map first
//circular references then are not a problem because they must
//put the stuff in order
for(int i = 0; i < depends.length; i++) {
Package dependsPackage = (Package)nameToPackage.get(depends[i].getName());
if(dependsPackage == null) {
throw new RuntimeException("package name="+p.getName()+" did not\n" +
"have "+depends[i]+" listed before it. circularDesign is off\n"+
"so package="+p.getName()+" must be moved up in the xml file");
}
}
}
nameToPackage.put(p.getName(), p);
packageNameToPackage.put(p.getPackage(), p);
}
/**
* @param className Class name of a class our currentAliasPackage depends on.
*/
public void verifyDependencyOk(String className) {
log.log(" className="+className, Project.MSG_DEBUG);
if(className.startsWith("L"))
className = className.substring(1, className.length());
//get the classPackage our currentAliasPackage depends on....
String classPackage = VerifyDesignDelegate.getPackageName(className);
//check if this is an needdeclarations="false" package, if so, the dependency is ok if it
//is not declared
log.log(" classPackage="+classPackage, Project.MSG_DEBUG);
Package p = retreivePack(classPackage);
if(p == null) {
throw new BuildException(getErrorMessage(currentClass, className), location);
}
p.setUsed(true); //set package to used since we have classes in it
if(p != null && !p.isNeedDeclarations())
return;
String pack = currentAliasPackage.getPackage();
log.log(" AllowedDepends="+pack, Project.MSG_DEBUG);
log.log(" CurrentDepends="+className, Project.MSG_DEBUG);
if(isClassInPackage(className, currentAliasPackage))
return;
Depends[] depends = currentAliasPackage.getDepends();
//probably want to create a regular expression out of all the depends and just match on that
//each time. for now though, just get it working and do the basic(optimize later if needed)
for(int i = 0; i < depends.length; i++) {
Depends d = depends[i];
String name = d.getName();
Package temp = getPackage(name);
log.log(" AllowedDepends="+temp.getPackage(), Project.MSG_DEBUG);
log.log(" CurrentDepends="+className, Project.MSG_DEBUG);
if(isClassInPackage(className, temp)) {
temp.setUsed(true); //set package to used since we are depending on it(could be external package like junit)
currentAliasPackage.addUsedDependency(d);
return;
}
}
log.log("***************************************", Project.MSG_DEBUG);
log.log("***************************************", Project.MSG_DEBUG);
throw new BuildException(Design.getErrorMessage(currentClass, className), location);
}
public boolean isClassInPackage(String className, Package p) {
String classPackage = VerifyDesignDelegate.getPackageName(className);
if(p.isIncludeSubpackages()) {
if(className.startsWith(p.getPackage()))
return true;
} else { //if not including subpackages, the it must be the exact package.
if(classPackage.equals(p.getPackage()))
return true;
}
return false;
}
/**
* @param className
* @return whether or not this class needs to be checked. (ie. if the
* attribute needdepends=false, we don't care about this package.
*/
public boolean needEvalCurrentClass(String className) {
currentClass = className;
String packageName = VerifyDesignDelegate.getPackageName(className);
// log("class="+className, Project.MSG_DEBUG);
if(!packageName.equals(currentPackageName) || currentAliasPackage == null) {
currentPackageName = packageName;
log.log("\nEvaluating package="+currentPackageName, Project.MSG_INFO);
currentAliasPackage = retreivePack(packageName);
//DEANDO: test this scenario
if(currentAliasPackage == null) {
log.log(" class="+className, Project.MSG_VERBOSE);
throw new BuildException(getNoDefinitionError(className), location);
}
currentAliasPackage.setUsed(true);
}
log.log(" class="+className, Project.MSG_VERBOSE);
if(packageName.equals(Package.DEFAULT)) {
if(className.indexOf('.') != -1) {
throw new RuntimeException("Internal Error");
}
} else if(!className.startsWith(currentPackageName))
throw new RuntimeException("Internal Error");
if(!currentAliasPackage.getNeedDepends())
return false;
return true;
}
public String getCurrentClass() {
return currentClass;
}
void checkClass(String dependsOn) {
log.log(" dependsOn1="+dependsOn, Project.MSG_DEBUG);
if(dependsOn.endsWith("[]")) {
int index = dependsOn.indexOf("[");
dependsOn = dependsOn.substring(0, index);
log.log(" dependsOn2="+dependsOn, Project.MSG_DEBUG);
}
if(primitives.contains(dependsOn))
return;
//Anything in java.lang package seems to be passed in as just the
//className with no package like Object, String or Class, so here we try to
//see if the name is a java.lang class....
String tempTry = "java.lang."+dependsOn;
try {
Class c = VerifyDesign.class.getClassLoader().loadClass(tempTry);
return;
} catch(ClassNotFoundException e) {
//not found, continue on...
}
//sometimes instead of passing java.lang.String or java.lang.Object, the bcel
//passes just String or Object
// if("String".equals(dependsOn) || "Object".equals(dependsOn))
// return;
verifyDependencyOk(dependsOn);
}
public static String getErrorMessage(String className, String dependsOnClass) {
String s = "\nYou are violating your own design...." +
"\nClass = "+className+" depends on\nClass = "+dependsOnClass+
"\nThe dependency to allow this is not defined in your design" +
"\nPackage="+VerifyDesignDelegate.getPackageName(className)+" is not defined to depend on"+
"\nPackage="+VerifyDesignDelegate.getPackageName(dependsOnClass)+
"\nChange the code or the design";
return s;
}
public static String getNoDefinitionError(String className) {
String s = "\nPackage="+VerifyDesignDelegate.getPackageName(className)+" is not defined in the design.\n"+
"All packages with classes must be declared in the design file\n"+
"Class found in the offending package="+className;
return s;
}
public static String getWrapperMsg(File originalFile, String message) {
String s = "\nThe file '" + originalFile.getAbsolutePath() + "' failed due to: " + message;
return s;
}
/**
* @param designErrors
*/
public void fillInUnusedPackages(Vector designErrors)
{
Collection values = nameToPackage.values();
Iterator iterator = values.iterator();
while(iterator.hasNext()) {
Package pack = (Package)iterator.next();
if(!pack.isUsed()) {
String msg = "Package name="+pack.getName()+" is unused. Full package="+pack.getPackage();
log.log(msg, Project.MSG_ERR);
designErrors.add(new BuildException(msg));
} else {
fillInUnusedDepends(designErrors, pack);
}
}
}
/**
* @param designErrors
* @param pack
*/
private void fillInUnusedDepends(Vector designErrors, Package pack)
{
Iterator iterator = pack.getUnusedDepends().iterator();
while(iterator.hasNext()) {
Depends depends = (Depends)iterator.next();
String msg = "Package name="+pack.getName()+" has a dependency declared that is not true anymore. Please erase the dependency <depends>"+depends.getName()+"</depends> from package="+pack.getName();
log.log(msg, Project.MSG_ERR);
designErrors.add(new BuildException(msg));
}
}
}