001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.compiler.grammar;
020:
021: import org.apache.beehive.netui.compiler.AnnotationMemberType;
022: import org.apache.beehive.netui.compiler.AnnotationGrammar;
023: import org.apache.beehive.netui.compiler.CompilerUtils;
024: import org.apache.beehive.netui.compiler.FlowControllerInfo;
025: import org.apache.beehive.netui.compiler.FatalCompileTimeException;
026: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationValue;
027: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
028: import org.apache.beehive.netui.compiler.typesystem.declaration.MemberDeclaration;
029: import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationTypeElementDeclaration;
030: import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
031: import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
032: import org.apache.beehive.netui.compiler.typesystem.declaration.PackageDeclaration;
033: import org.apache.beehive.netui.compiler.typesystem.env.CoreAnnotationProcessorEnv;
034:
035: import java.net.URISyntaxException;
036: import java.net.URI;
037: import java.io.File;
038:
039: /**
040: * String type that emits a warning if the given path does not exist in the webapp for this pageflow.
041: */
042: public class WebappPathType extends AnnotationMemberType {
043: private static final String[] CHECKABLE_EXTENSIONS = {
044: JSP_FILE_EXTENSION, XJSP_FILE_EXTENSION,
045: JPF_FILE_EXTENSION, "xml", "htm", "html" };
046:
047: private boolean _pathMustBeRelative = false;
048: private FlowControllerInfo _flowControllerInfo;
049:
050: public WebappPathType(boolean pathMustBeRelative,
051: String requiredRuntimeVersion,
052: AnnotationGrammar parentGrammar, FlowControllerInfo fcInfo) {
053: super (requiredRuntimeVersion, parentGrammar);
054: _pathMustBeRelative = pathMustBeRelative;
055: _flowControllerInfo = fcInfo;
056: }
057:
058: private static boolean isCheckableExtension(String filePath) {
059: for (int i = 0; i < CHECKABLE_EXTENSIONS.length; ++i) {
060: if (filePath.endsWith(CHECKABLE_EXTENSIONS[i]))
061: return true;
062: }
063:
064: return false;
065: }
066:
067: public Object onCheck(AnnotationTypeElementDeclaration valueDecl,
068: AnnotationValue value,
069: AnnotationInstance[] parentAnnotations,
070: MemberDeclaration classMember, int annotationArrayIndex)
071: throws FatalCompileTimeException {
072: String filePath = (String) value.getValue();
073:
074: //
075: // First make sure it's a valid URI.
076: //
077: try {
078: URI uri = new URI(filePath);
079: filePath = uri.getPath(); // decodes the path
080: } catch (URISyntaxException e) {
081: addError(value, "error.invalid-uri", e
082: .getLocalizedMessage());
083: return null;
084: }
085:
086: //
087: // The path will be null for an 'opaque' URI, like "news:comp.lang.java".
088: //
089: if (filePath == null || filePath.length() == 0)
090: return null;
091:
092: //
093: // Make sure it's a filetype that should exist on the filesystem. If not, ignore it.
094: //
095: if (!checkAnyExtension() && !isCheckableExtension(filePath))
096: return null;
097:
098: boolean fileExists = true;
099: TypeDeclaration outerClass = CompilerUtils
100: .getOutermostClass(classMember);
101: File fileToCheck = null;
102:
103: if (filePath.charAt(0) == '/') // relative to webapp root
104: {
105: if (_pathMustBeRelative)
106: addError(value, "error.relative-uri");
107:
108: if (filePath.endsWith(JPF_FILE_EXTENSION_DOT)) {
109: TypeDeclaration type = CompilerUtils.inferTypeFromPath(
110: filePath, getEnv());
111: fileToCheck = type != null ? CompilerUtils
112: .getSourceFile(type, false) : null;
113: if (fileToCheck == null || !fileToCheck.exists()) {
114: fileExists = false;
115: }
116: } else {
117: File jpfSourceFile = CompilerUtils
118: .getSourceFile(CompilerUtils
119: .getOuterClass(classMember), false);
120:
121: //
122: // We don't always have the source file for the classMember's containing class (e.g., when this class
123: // extends a class that's on classpath but not sourcepath). If we don't have the source, just ignore.
124: //
125: if (jpfSourceFile != null) {
126: fileToCheck = CompilerUtils.getWebappRelativeFile(
127: filePath, allowFileInPageFlowSourceDir(),
128: getEnv());
129:
130: if (fileToCheck != null
131: && !fileToCheck.exists()
132: && !(ignoreDirectories() && fileToCheck
133: .isDirectory())) {
134: fileExists = false;
135: }
136: }
137: }
138: }
139:
140: //
141: // If the class being compiled is abstract, don't print warnings for relative-path files that aren't
142: // found. The derived class might have them.
143: //
144: else if (filePath.indexOf('/') != 0
145: && !outerClass.hasModifier(Modifier.ABSTRACT)) {
146: CompilerUtils.Mutable retFileToCheck = new CompilerUtils.Mutable();
147: fileExists = checkRelativePath(filePath, outerClass,
148: retFileToCheck, ignoreDirectories(),
149: allowFileInPageFlowSourceDir(), getEnv());
150: fileToCheck = (File) retFileToCheck.get();
151: }
152:
153: if (fileExists) {
154: if (fileToCheck != null)
155: runAdditionalChecks(fileToCheck, value);
156: } else {
157: if (doFatalError()) {
158: addError(value, "error.file-not-found", filePath);
159: } else {
160: addWarning(value, "warning.file-not-found", filePath);
161: }
162: }
163:
164: if (fileToCheck != null)
165: _flowControllerInfo.addReferencedFile(fileToCheck);
166:
167: return null;
168: }
169:
170: public static boolean relativePathExists(String filePath,
171: TypeDeclaration outerClass, CoreAnnotationProcessorEnv env)
172: throws FatalCompileTimeException {
173: assert filePath.charAt(0) != '/' : filePath
174: + " is not a relative path";
175: if (!isCheckableExtension(filePath))
176: return true;
177: return checkRelativePath(filePath, outerClass, null, true,
178: false, env);
179: }
180:
181: private static boolean checkRelativePath(String filePath,
182: TypeDeclaration outerClass,
183: CompilerUtils.Mutable retFileToCheck,
184: boolean ignoreDirectories,
185: boolean allowFileInPageFlowSourceDir,
186: CoreAnnotationProcessorEnv env)
187: throws FatalCompileTimeException
188:
189: {
190: File fileToCheck = null;
191: boolean fileExists = true;
192:
193: if (filePath.endsWith(JPF_FILE_EXTENSION_DOT)) {
194: String className = filePath.substring(0, filePath.length()
195: - JPF_FILE_EXTENSION_DOT.length());
196: String pkg = outerClass.getPackage().getQualifiedName();
197:
198: // try to normalize the path
199: boolean normalized = className.charAt(0) != '.';
200: while (!normalized) {
201: if (className.startsWith("../")
202: && className.length() > 3) {
203: className = className.substring(3);
204: int lastDot = pkg.lastIndexOf('.');
205: pkg = lastDot != -1 ? pkg.substring(0, lastDot)
206: : "";
207: } else if (className.startsWith("./")
208: && className.length() > 2) {
209: className = className.substring(2);
210: } else {
211: normalized = true;
212: }
213: }
214: className = (pkg.length() > 0 ? pkg + '.' : "")
215: + className.replace('/', '.');
216: TypeDeclaration type = env.getTypeDeclaration(className);
217: fileToCheck = type != null ? CompilerUtils.getSourceFile(
218: type, false) : null;
219: if (fileToCheck == null)
220: fileExists = false;
221: }
222: // In certain error conditions (jpfFile == null), we can't determine the file. In this case, just ignore.
223: else if (CompilerUtils.getSourceFile(outerClass, false) != null) {
224: if (allowFileInPageFlowSourceDir) {
225: fileToCheck = CompilerUtils
226: .getFileRelativeToSourceFile(outerClass,
227: filePath, env);
228: } else {
229: // Use the package name to infer the relative path (from web content root) to the page flow directory.
230: String[] webContentRoots = CompilerUtils
231: .getWebContentRoots(env);
232: String jpfParentRelativePath = "";
233: PackageDeclaration jpfPackage = outerClass.getPackage();
234: if (jpfPackage != null)
235: jpfParentRelativePath = jpfPackage
236: .getQualifiedName().replace('.', '/');
237:
238: for (int i = 0; i < webContentRoots.length; i++) {
239: String webContentRoot = webContentRoots[i];
240: File desiredParentDir = new File(webContentRoot,
241: jpfParentRelativePath);
242: fileToCheck = new File(desiredParentDir, filePath);
243: if (fileToCheck.exists())
244: break;
245: }
246: }
247:
248: if (fileToCheck == null || !fileToCheck.exists()) {
249: fileExists = false;
250: }
251: }
252:
253: if (retFileToCheck != null)
254: retFileToCheck.set(fileToCheck);
255: return fileExists;
256: }
257:
258: protected boolean checkAnyExtension() {
259: return false;
260: }
261:
262: protected boolean doFatalError() {
263: return false;
264: }
265:
266: protected void runAdditionalChecks(File file, AnnotationValue member) {
267: }
268:
269: protected boolean ignoreDirectories() {
270: return true;
271: }
272:
273: /**
274: * Tell whether the file must be in the page flow directory. If not, it is assumed to live in a webapp-addressable
275: * directory whose path corresponds to the page flow's package. This is here to support page flows in WEB-INF.
276: */
277: protected boolean allowFileInPageFlowSourceDir() {
278: return false;
279: }
280:
281: protected FlowControllerInfo getFlowControllerInfo() {
282: return _flowControllerInfo;
283: }
284: }
|