/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
* $Header:$
*/
package org.apache.beehive.netui.compiler.grammar;
import org.apache.beehive.netui.compiler.AnnotationMemberType;
import org.apache.beehive.netui.compiler.AnnotationGrammar;
import org.apache.beehive.netui.compiler.CompilerUtils;
import org.apache.beehive.netui.compiler.FlowControllerInfo;
import org.apache.beehive.netui.compiler.FatalCompileTimeException;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationValue;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
import org.apache.beehive.netui.compiler.typesystem.declaration.MemberDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationTypeElementDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
import org.apache.beehive.netui.compiler.typesystem.declaration.PackageDeclaration;
import org.apache.beehive.netui.compiler.typesystem.env.CoreAnnotationProcessorEnv;
import java.net.URISyntaxException;
import java.net.URI;
import java.io.File;
/**
* String type that emits a warning if the given path does not exist in the webapp for this pageflow.
*/
public class WebappPathType
extends AnnotationMemberType
{
private static final String[] CHECKABLE_EXTENSIONS =
{
JSP_FILE_EXTENSION,
XJSP_FILE_EXTENSION,
JPF_FILE_EXTENSION,
"xml",
"htm",
"html"
};
private boolean _pathMustBeRelative = false;
private FlowControllerInfo _flowControllerInfo;
public WebappPathType( boolean pathMustBeRelative, String requiredRuntimeVersion, AnnotationGrammar parentGrammar,
FlowControllerInfo fcInfo )
{
super( requiredRuntimeVersion, parentGrammar );
_pathMustBeRelative = pathMustBeRelative;
_flowControllerInfo = fcInfo;
}
private static boolean isCheckableExtension( String filePath )
{
for ( int i = 0; i < CHECKABLE_EXTENSIONS.length; ++i )
{
if ( filePath.endsWith( CHECKABLE_EXTENSIONS[i] ) ) return true;
}
return false;
}
public Object onCheck( AnnotationTypeElementDeclaration valueDecl, AnnotationValue value,
AnnotationInstance[] parentAnnotations, MemberDeclaration classMember,
int annotationArrayIndex )
throws FatalCompileTimeException
{
String filePath = ( String ) value.getValue();
//
// First make sure it's a valid URI.
//
try
{
URI uri = new URI( filePath );
filePath = uri.getPath(); // decodes the path
}
catch ( URISyntaxException e )
{
addError( value, "error.invalid-uri", e.getLocalizedMessage() );
return null;
}
//
// The path will be null for an 'opaque' URI, like "news:comp.lang.java".
//
if ( filePath == null || filePath.length() == 0 ) return null;
//
// Make sure it's a filetype that should exist on the filesystem. If not, ignore it.
//
if ( ! checkAnyExtension() && ! isCheckableExtension( filePath ) ) return null;
boolean fileExists = true;
TypeDeclaration outerClass = CompilerUtils.getOutermostClass( classMember );
File fileToCheck = null;
if ( filePath.charAt( 0 ) == '/' ) // relative to webapp root
{
if ( _pathMustBeRelative ) addError( value, "error.relative-uri" );
if ( filePath.endsWith( JPF_FILE_EXTENSION_DOT ) )
{
TypeDeclaration type = CompilerUtils.inferTypeFromPath( filePath, getEnv() );
fileToCheck = type != null ? CompilerUtils.getSourceFile( type, false ) : null;
if (fileToCheck == null || ! fileToCheck.exists()) {
fileExists = false;
}
}
else
{
File jpfSourceFile = CompilerUtils.getSourceFile( CompilerUtils.getOuterClass( classMember ), false );
//
// We don't always have the source file for the classMember's containing class (e.g., when this class
// extends a class that's on classpath but not sourcepath). If we don't have the source, just ignore.
//
if ( jpfSourceFile != null )
{
fileToCheck = CompilerUtils.getWebappRelativeFile( filePath, allowFileInPageFlowSourceDir(), getEnv() );
if ( fileToCheck != null && ! fileToCheck.exists() && ! ( ignoreDirectories() && fileToCheck.isDirectory() ) )
{
fileExists = false;
}
}
}
}
//
// If the class being compiled is abstract, don't print warnings for relative-path files that aren't
// found. The derived class might have them.
//
else if ( filePath.indexOf( '/' ) != 0 && ! outerClass.hasModifier( Modifier.ABSTRACT ) )
{
CompilerUtils.Mutable retFileToCheck = new CompilerUtils.Mutable();
fileExists = checkRelativePath( filePath, outerClass, retFileToCheck, ignoreDirectories(),
allowFileInPageFlowSourceDir(), getEnv() );
fileToCheck = ( File ) retFileToCheck.get();
}
if ( fileExists )
{
if ( fileToCheck != null ) runAdditionalChecks( fileToCheck, value );
}
else
{
if ( doFatalError() )
{
addError( value, "error.file-not-found", filePath );
}
else
{
addWarning( value, "warning.file-not-found", filePath );
}
}
if ( fileToCheck != null ) _flowControllerInfo.addReferencedFile( fileToCheck );
return null;
}
public static boolean relativePathExists( String filePath, TypeDeclaration outerClass,
CoreAnnotationProcessorEnv env )
throws FatalCompileTimeException
{
assert filePath.charAt( 0 ) != '/' : filePath + " is not a relative path";
if ( ! isCheckableExtension( filePath ) ) return true;
return checkRelativePath( filePath, outerClass, null, true, false, env );
}
private static boolean checkRelativePath( String filePath, TypeDeclaration outerClass,
CompilerUtils.Mutable retFileToCheck,
boolean ignoreDirectories, boolean allowFileInPageFlowSourceDir,
CoreAnnotationProcessorEnv env )
throws FatalCompileTimeException
{
File fileToCheck = null;
boolean fileExists = true;
if ( filePath.endsWith( JPF_FILE_EXTENSION_DOT ) )
{
String className = filePath.substring( 0, filePath.length() - JPF_FILE_EXTENSION_DOT.length() );
String pkg = outerClass.getPackage().getQualifiedName();
// try to normalize the path
boolean normalized = className.charAt(0) != '.';
while (!normalized) {
if (className.startsWith("../") && className.length() > 3) {
className = className.substring( 3 );
int lastDot = pkg.lastIndexOf( '.' );
pkg = lastDot != -1 ? pkg.substring( 0, lastDot ) : "";
}
else if (className.startsWith("./") && className.length() > 2) {
className = className.substring( 2 );
}
else {
normalized = true;
}
}
className = ( pkg.length() > 0 ? pkg + '.' : "" ) + className.replace( '/', '.' );
TypeDeclaration type = env.getTypeDeclaration( className );
fileToCheck = type != null ? CompilerUtils.getSourceFile( type, false ) : null;
if ( fileToCheck == null ) fileExists = false;
}
// In certain error conditions (jpfFile == null), we can't determine the file. In this case, just ignore.
else if ( CompilerUtils.getSourceFile( outerClass, false ) != null )
{
if ( allowFileInPageFlowSourceDir )
{
fileToCheck = CompilerUtils.getFileRelativeToSourceFile( outerClass, filePath, env );
}
else
{
// Use the package name to infer the relative path (from web content root) to the page flow directory.
String[] webContentRoots = CompilerUtils.getWebContentRoots( env );
String jpfParentRelativePath = "";
PackageDeclaration jpfPackage = outerClass.getPackage();
if ( jpfPackage != null ) jpfParentRelativePath = jpfPackage.getQualifiedName().replace( '.', '/' );
for ( int i = 0; i < webContentRoots.length; i++ )
{
String webContentRoot = webContentRoots[i];
File desiredParentDir = new File( webContentRoot, jpfParentRelativePath );
fileToCheck = new File( desiredParentDir, filePath );
if ( fileToCheck.exists() ) break;
}
}
if (fileToCheck == null || ! fileToCheck.exists()) {
fileExists = false;
}
}
if ( retFileToCheck != null ) retFileToCheck.set( fileToCheck );
return fileExists;
}
protected boolean checkAnyExtension()
{
return false;
}
protected boolean doFatalError()
{
return false;
}
protected void runAdditionalChecks( File file, AnnotationValue member )
{
}
protected boolean ignoreDirectories()
{
return true;
}
/**
* Tell whether the file must be in the page flow directory. If not, it is assumed to live in a webapp-addressable
* directory whose path corresponds to the page flow's package. This is here to support page flows in WEB-INF.
*/
protected boolean allowFileInPageFlowSourceDir()
{
return false;
}
protected FlowControllerInfo getFlowControllerInfo()
{
return _flowControllerInfo;
}
}