/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* WorkspaceLoader.java
* Creation date: Feb 27, 2003.
* By: Edward Lam
*/
package org.openquark.cal.services;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.util.ClassInfo;
import org.openquark.util.General;
import org.openquark.util.Pair;
import org.openquark.util.TextEncodingUtilities;
/**
* A class responsible for loading a program that comprises an initial workspace.
* @author Edward Lam
*/
public class WorkspaceLoader {
private static final String LINE_COMMENT_CHARS = "//";
private static final String BEGIN_BLOCK_COMMENT_CHARS = "/*";
private static final String END_BLOCK_COMMENT_CHARS = "*/";
/**
* Encapsulates the various pieces of information built up by {@link WorkspaceLoader#getWorkspaceDeclarationInfoHelper}
* and returned by {@link WorkspaceLoader#getWorkspaceDeclarationInfo}.
*
* @author Joseph Wong
*/
private static final class WorkspaceDeclarationInfo {
/**
* names of workspace declarations already traversed.
*/
private final Set<String> traversedDeclarationNamesSet;
/**
* Map from module name to the stored module for that module
* for each module which has been traversed.
*/
private final LinkedHashMap<ModuleName, StoredVaultElement.Module> nameToStoredModuleMap;
/**
* A map containing the names of the modules included by the workspace declaration and
* their associated entries in all the workspace declarations in the tree.
*/
private final Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap;
/**
* A Map mapping a workspace declaration name to a LinkedHashSet of other declarations it imports.
*/
private final Map<String, LinkedHashSet<String>> importsMap;
/**
* Constructs a WorkspaceDeclarationInfo.
*/
WorkspaceDeclarationInfo(Set<String> traversedDeclarationNamesSet, LinkedHashMap<ModuleName, StoredVaultElement.Module> nameToStoredModuleMap, Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap, Map<String, LinkedHashSet<String>> importsMap) {
this.traversedDeclarationNamesSet = traversedDeclarationNamesSet;
this.nameToStoredModuleMap = nameToStoredModuleMap;
this.moduleNameToWorkspaceDeclInfoMap = moduleNameToWorkspaceDeclInfoMap;
this.importsMap = importsMap;
}
/**
* @return the {@link WorkspaceLoader.WorkspaceDeclarationInfo#importsMap}.
*/
Map<String, LinkedHashSet<String>> getImportsMap() {
return importsMap;
}
/**
* @return the {@link WorkspaceLoader.WorkspaceDeclarationInfo#moduleNameToWorkspaceDeclInfoMap}.
*/
Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> getModuleNameToWorkspaceDeclInfoMap() {
return moduleNameToWorkspaceDeclInfoMap;
}
/**
* @return the {@link WorkspaceLoader.WorkspaceDeclarationInfo#nameToStoredModuleMap}.
*/
Map<ModuleName, StoredVaultElement.Module> getNameToStoredModuleMap() {
return nameToStoredModuleMap;
}
/**
* @return the {@link WorkspaceLoader.WorkspaceDeclarationInfo#traversedDeclarationNamesSet}.
*/
Set<String> getTraversedDeclarationNamesSet() {
return traversedDeclarationNamesSet;
}
}
/**
* Get a file object from a path
* @param path the string representation of the path. This can be a path relative to the classloader, or a URI.
* @return File the corresponding file, or null if the path is invalid or the file does not exists.
*/
public static File fileFromPath(String path) {
// Check for a workspace file defined by a system property.
try {
URI uri = new URI(path);
return new File(uri);
} catch (URISyntaxException mue) { // bad uri
} catch (IllegalArgumentException iae) { // invalid param
}
// The argument for url and uri constructors differ slightly - url accepts spaces, but for the uri they must be encoded.
try {
URL url = new URL(path);
return new File(urlToUri(url));
} catch (MalformedURLException e) { // bad url
} catch (IllegalArgumentException iae) { // invalid param
}
// Attempt to parse the pathname relative to the current directory (relative to the class loader).
URL resource = ClassInfo.getResource(path);
if (resource == null && path.startsWith("/")) {
// Try again if we have a leading slash..
resource = ClassInfo.getResource(path.substring(1));
}
if (resource == null) {
return null;
}
String fileName = General.decodeUrlUtf8(resource.getFile());
File fileFromProperty = new File(fileName);
if (fileFromProperty.exists()) {
return fileFromProperty;
}
return null;
}
/**
* Get the names of the workspace declarations required by the given workspace declaration.
* ie. Get the declarations on which a given declaration depends.
* @param workspaceDeclarationProvider the workspace declaration to analyse.
* @param workspace the cal workspace.
* @param status the tracking status object.
* @return the names of the workspace declarations on which the given workspace depends.
* This set will include the name of the provided workspace itself.
*/
public static Set<String> getRequiredDeclarationNames(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider,
CALWorkspace workspace, Status status) {
WorkspaceDeclarationInfo info = getWorkspaceDeclarationInfo(workspaceDeclarationProvider, workspace.getVaultRegistry(), status);
return info.getTraversedDeclarationNamesSet();
}
/**
* Get the names of the modules included directly or indirectly in the given workspace declaration.
* @param workspaceDeclarationProvider the workspace declaration to analyse.
* @param workspace the CAL workspace.
* @param status the tracking status object.
* @return a set containing the names of the modules included by the workspace declaration.
*/
public static Set<ModuleName> getStoredModuleNames(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider,
CALWorkspace workspace, Status status) {
WorkspaceDeclarationInfo info = getWorkspaceDeclarationInfo(workspaceDeclarationProvider, workspace.getVaultRegistry(), status);
// the key set of nameToStoredModuleMap is the set of module names
return info.getNameToStoredModuleMap().keySet();
}
/**
* Get the names of the modules included directly or indirectly in the given workspace declaration, in the form
* of a map (module name -> LinkedHashSet of workspace declaration names) that provides the names of the workspace declarations
* in which a particular module is declared (with the first name in the list being the one that counts, while the
* remaining workspace declarations contained entries that are shadowed).
*
* @param workspaceDeclarationProvider the workspace declaration to analyse.
* @param workspace the CAL workspace.
* @param status the tracking status object.
* @return a map containing the names of the modules included by the workspace declaration and
* their associated entries in all the workspace declarations in the tree.
*/
public static Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>>
getStoredModuleNameToWorkspaceDeclNamesMap(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider, CALWorkspace workspace, Status status) {
WorkspaceDeclarationInfo info = getWorkspaceDeclarationInfo(workspaceDeclarationProvider, workspace.getVaultRegistry(), status);
return info.getModuleNameToWorkspaceDeclInfoMap();
}
/**
* Get a map containing information about which workspace declaration imports which other declarations.
*
* @param workspaceDeclarationProvider the workspace declaration to analyse.
* @param workspace the CAL workspace.
* @param status the tracking status object.
* @return a map mapping a workspace declaration name to a LinkedHashSet of other declarations it imports.
*/
public static Map<String, LinkedHashSet<String>>
getWorkspaceDeclarationImportsMap(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider, CALWorkspace workspace, Status status) {
WorkspaceDeclarationInfo info = getWorkspaceDeclarationInfo(workspaceDeclarationProvider, workspace.getVaultRegistry(), status);
return info.getImportsMap();
}
/**
* Get the stored module from a reader on a workspace specification.
* @param workspaceDeclarationProvider the provider for the workspace spec. Should not be null.
* @param vaultRegistry the registry to use to look up the paths referenced by the workspace definition.
* @param status the tracking status object.
* @return the stored modules corresponding to the specification, or null if the workspace declaration provider is null.
* WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider
*/
static StoredVaultElement.Module[] getStoredModules(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider,
VaultRegistry vaultRegistry, Status status) {
// Defer to the helper method.
WorkspaceDeclarationInfo info = getWorkspaceDeclarationInfo(workspaceDeclarationProvider, vaultRegistry, status);
// Convert to an array and return.
Collection<StoredVaultElement.Module> storedModuleSet = info.getNameToStoredModuleMap().values();
return storedModuleSet.toArray(new StoredVaultElement.Module[storedModuleSet.size()]);
}
/**
* Helper method for the various API methods of this class that obtains information contained in a workspace declaration file.
* Collects declared modules, and also processes imported workspace declarations.
*
* @param workspaceDeclarationProvider the provider for the workspace declaration which should be looked at.
* @param vaultRegistry the vault registry to use to instantiate vaults.
* @param status the tracking status object.
*/
private static WorkspaceDeclarationInfo getWorkspaceDeclarationInfo(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider, VaultRegistry vaultRegistry, Status status) {
// names of workspace declarations already traversed.
Set<String> traversedDeclarationNamesSet = new HashSet<String>();
// Map from module name to the stored module for that module
// for each module which has been traversed.
LinkedHashMap<ModuleName, StoredVaultElement.Module> nameToStoredModuleMap = new LinkedHashMap<ModuleName, StoredVaultElement.Module>();
// A map containing the names of the modules included by the workspace declaration and
// their associated entries in all the workspace declarations in the tree.
Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap = new HashMap<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>>();
// A Map mapping a workspace declaration name to a LinkedHashSet of other declarations it imports.
Map<String, LinkedHashSet<String>> importsMap = new HashMap<String, LinkedHashSet<String>>();
getWorkspaceDeclarationInfoHelper(
workspaceDeclarationProvider,
vaultRegistry,
traversedDeclarationNamesSet,
nameToStoredModuleMap,
moduleNameToWorkspaceDeclInfoMap,
importsMap,
status);
return new WorkspaceDeclarationInfo(traversedDeclarationNamesSet, nameToStoredModuleMap, moduleNameToWorkspaceDeclInfoMap, importsMap);
}
/**
* Helper method for getWorkspaceDeclarationInfo().
* Collects declared modules, and calls itself recursively on declared workspace declarations.
*
* @param workspaceDeclarationProvider the provider for the workspace declaration which should be looked at.
* @param vaultRegistry the vault registry to use to instantiate vaults.
* @param traversedDeclarationNamesSet names of workspace declarations already traversed.
* This will be populated by this method.
* @param nameToStoredModuleMap Map from module name to the stored module for that module
* for each module which has been traversed. This map will be populated by this method.
* @param moduleNameToWorkspaceDeclInfoMap a (Map<String, LinkedHashSet<Pair<String, VaultElementInfo>>>)
* containing the names of the modules included by the workspace declaration and
* their associated entries in all the workspace declarations in the tree. This will be populated by this method.
* @param importsMap a Map mapping a workspace declaration name to a LinkedHashSet of other declarations it imports.
* @param status the tracking status object.
*/
private static void getWorkspaceDeclarationInfoHelper(WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider, VaultRegistry vaultRegistry,
Set<String> traversedDeclarationNamesSet, Map<ModuleName, StoredVaultElement.Module> nameToStoredModuleMap, Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap, Map<String, LinkedHashSet<String>> importsMap, Status status) {
// Check whether we've already traversed a workspace declaration with this name.
final String workspaceDeclarationName = workspaceDeclarationProvider.getName();
if (traversedDeclarationNamesSet.contains(workspaceDeclarationName)) {
return;
}
// Add to the set of workspace declarations we've traversed.
traversedDeclarationNamesSet.add(workspaceDeclarationName);
// Add an entry of the workspace declarations to the importsMap.
LinkedHashSet<String> imports = importsMap.get(workspaceDeclarationName);
if (imports == null) {
imports = new LinkedHashSet<String>();
importsMap.put(workspaceDeclarationName, imports);
}
// Get an input stream on the workspace declaration.
InputStream providerInputStream = workspaceDeclarationProvider.getInputStream(vaultRegistry, status);
if (providerInputStream == null) {
String message = "The specified workspace declaration does not exist: " + workspaceDeclarationProvider.getName();
status.add(new Status(Status.Severity.ERROR, message, null));
return;
}
// the names of modules specified in the current workspace declaration only. For error-checking.
Set<ModuleName> moduleNamesInCurrentDeclaration = new HashSet<ModuleName>();
// Lex the file into lines of tokens.
List<Pair<Integer, List<String>>> workspaceDeclarationLinesList;
try {
workspaceDeclarationLinesList = lexWorkspaceDeclarationStream(TextEncodingUtilities.makeUTF8Reader(providerInputStream), status);
} finally {
try {
providerInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
for (final Pair<Integer, List<String>> lineNumTokensPair : workspaceDeclarationLinesList) {
int lineNum = (lineNumTokensPair.fst()).intValue();
List<String> lineTokens = lineNumTokensPair.snd();
// Check for an import directive
if (!lineTokens.isEmpty() && lineTokens.get(0).equals("import")) {
if (lineTokens.size() > 2 && lineTokens.get(1).equals("car")) {
// importing a Car file.
// the syntax for importing a Car in the workspace declaration is:
//
// import car <declaration for car file> <car workspace spec name>
//
// where <declaration for car file> is a declaration of the car file including
// the vault in which it is stored, e.g. StandardVault test.car
//
// So, for example, a complete declaration line may look like this:
//
// import car StandardVault test.car main.carspec
// Get the Car workspace spec name.
String specName = lineTokens.get(lineTokens.size() - 1);
// Get the info for the vault element.
lineTokens = lineTokens.subList(2, lineTokens.size() - 1);
VaultElementInfo vaultElementInfo = VaultElementInfo.Basic.makeFromDeclarationString(lineTokens, status);
if (vaultElementInfo == null) {
continue;
}
// Get the Car
StoredVaultElement.Car car = vaultRegistry.getStoredCar(vaultElementInfo, status);
if (car == null) {
status.add(new Status(Status.Severity.WARNING, "Error reading vault element from " + workspaceDeclarationName + ", line: " + lineNum, null));
continue;
}
// Read the Car workspace spec.
ModuleName[] moduleNames = getModulesInWorkspaceSpec(car, specName, status);
// Read the resources from the Car vault
CarVault carVault = car.getCarVault();
Set<ModuleName> moduleNamesInCar = new HashSet<ModuleName>();
for (final ModuleName element : moduleNames) {
StoredVaultElement.Module storedModule = carVault.getStoredModule(element, 0, status);
addStoredModuleToMapHelper(storedModule, workspaceDeclarationName, moduleNamesInCar, nameToStoredModuleMap, moduleNameToWorkspaceDeclInfoMap, status);
}
} else {
// importing another workspace declaration.
lineTokens = lineTokens.subList(1, lineTokens.size());
// Get the info for the vault element.
VaultElementInfo vaultElementInfo = VaultElementInfo.Basic.makeFromDeclarationString(lineTokens, status);
if (vaultElementInfo == null) {
status.add(new Status(Status.Severity.WARNING, "Error reading vault element from " + workspaceDeclarationName + ", line: " + lineNum, null));
continue;
}
// Get the stored workspace declaration.
StoredVaultElement.WorkspaceDeclaration workspaceDeclaration = vaultRegistry.getStoredWorkspaceDeclaration(vaultElementInfo, status);
if (workspaceDeclaration == null) {
status.add(new Status(Status.Severity.WARNING, "Error reading vault element from " + workspaceDeclarationName + ", line: " + lineNum, null));
continue;
}
// Wrap as a declaration provider.
WorkspaceDeclaration.StreamProvider workspaceDeclarationProviderFromLine =
DefaultWorkspaceDeclarationProvider.getDefaultWorkspaceDeclarationProvider(vaultElementInfo);
imports.add(workspaceDeclarationProviderFromLine.getName());
// Recursive call.
getWorkspaceDeclarationInfoHelper(workspaceDeclarationProviderFromLine, vaultRegistry,
traversedDeclarationNamesSet, nameToStoredModuleMap, moduleNameToWorkspaceDeclInfoMap, importsMap, status);
}
} else {
// specifying a stored module.
VaultElementInfo vaultElementInfo = VaultElementInfo.Basic.makeFromDeclarationString(lineTokens, status);
if (vaultElementInfo == null) {
status.add(new Status(Status.Severity.WARNING, "Invalid module declaration in " + workspaceDeclarationName + ", line: " + lineNum, null));
continue;
}
StoredVaultElement.Module storedModule = vaultRegistry.getStoredModule(vaultElementInfo, status);
if (storedModule == null) {
status.add(new Status(Status.Severity.WARNING, "Error reading vault element from " + workspaceDeclarationName + ", line: " + lineNum, null));
continue;
}
addStoredModuleToMapHelper(storedModule, workspaceDeclarationName, moduleNamesInCurrentDeclaration, nameToStoredModuleMap, moduleNameToWorkspaceDeclInfoMap, status);
}
}
}
/**
* Retrieves the list of module names contained in the specified Car workspace spec file.
*
* The spec file format is a simple whitespace-delimited list of module names that also allows
* both slash-slash single line comments and C-style block comments.
*
* @param car the Car from which the spec file is to be fetched.
* @param specName the name of the spec file.
* @param status the status tracking object.
* @return the array of module names contained in the spec file.
*/
private static ModuleName[] getModulesInWorkspaceSpec(StoredVaultElement.Car car, String specName, Status status) {
InputStream workspaceSpecInputStream = car.getWorkspaceSpec(specName, status);
if (workspaceSpecInputStream == null) {
status.add(new Status(Status.Severity.ERROR, "Cannot read the workspace spec " + specName + " in the Car " + car.getName(), null));
return new ModuleName[0];
}
BufferedReader reader = new BufferedReader(TextEncodingUtilities.makeUTF8Reader(workspaceSpecInputStream));
try {
StringBuilder moduleNamesInWhitespaceAndCommentSeparatedList = new StringBuilder();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
// trim the leading whitespace to make the single-line comment handling easier
line = line.trim();
if (!line.startsWith("//")) { // handle slash-slash style single-line comments
moduleNamesInWhitespaceAndCommentSeparatedList.append(line).append(' ');
}
}
// get rid of the C-style block comments
String moduleNamesInWhitespaceSeparatedList =
moduleNamesInWhitespaceAndCommentSeparatedList.toString().replaceAll("/\\*([^*]|\\*[^/])*\\*/", "");
// tokenize the remaining content into a list of module names (separated by whitespace)
StringTokenizer tokenizer = new StringTokenizer(moduleNamesInWhitespaceSeparatedList);
List<ModuleName> moduleNameList = new ArrayList<ModuleName>();
while (tokenizer.hasMoreTokens()) {
moduleNameList.add(ModuleName.make(tokenizer.nextToken()));
}
return moduleNameList.toArray(new ModuleName[moduleNameList.size()]);
} catch (IOException e) {
status.add(new Status(Status.Severity.ERROR, "Cannot read the workspace spec " + specName + " in the Car " + car.getName(), null));
return new ModuleName[0];
}
}
/**
* Adds the given stored module to the given stored module map, while updating the set of module names already seen.
* @param storedModule the stored module to be added to the map.
* @param workspaceDeclarationName the name of the workspace declaration currently being processed.
* @param moduleNamesInCurrentDeclaration the set of module names that have already been processed - this set will be updated.
* @param nameToStoredModuleMap the stored module map to which the given stored module is to be added.
* @param moduleNameToWorkspaceDeclInfoMap a map containing the names of the modules included by the workspace declaration and
* their associated entries in all the workspace declarations in the tree. This will be populated by this method.
* @param status the tracking status object.
*/
private static void addStoredModuleToMapHelper(StoredVaultElement.Module storedModule, String workspaceDeclarationName, Set<ModuleName> moduleNamesInCurrentDeclaration, Map<ModuleName, StoredVaultElement.Module> nameToStoredModuleMap, Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap, Status status) {
ModuleName moduleName = ModuleName.maybeMake(storedModule.getName());
if (moduleName == null) {
status.add(new Status(Status.Severity.ERROR, "The name " + storedModule.getName() + " is not a valid module name", null));
}
// Add to the stored module map if a stored module by that name doesn't already exist in the map.
if (!nameToStoredModuleMap.containsKey(moduleName)) {
nameToStoredModuleMap.put(moduleName, storedModule);
}
LinkedHashSet<Pair<String, VaultElementInfo>> workspaceDeclarationNamesForModule = moduleNameToWorkspaceDeclInfoMap.get(moduleName);
if (workspaceDeclarationNamesForModule == null) {
workspaceDeclarationNamesForModule = new LinkedHashSet<Pair<String, VaultElementInfo>>();
moduleNameToWorkspaceDeclInfoMap.put(moduleName, workspaceDeclarationNamesForModule);
}
workspaceDeclarationNamesForModule.add(new Pair<String, VaultElementInfo>(workspaceDeclarationName, storedModule.getVaultInfo()));
// Signal a warning if the same module appears twice in the same declaration.
if (moduleNamesInCurrentDeclaration.contains(moduleName)) {
status.add(new Status(Status.Severity.WARNING, "Module " + moduleName + " appears twice in declaration " + workspaceDeclarationName, null));
}
// Add to the set of module names in the current declaration.
moduleNamesInCurrentDeclaration.add(moduleName);
}
/**
* Lex a stream on a workspace declaration into lines of tokens.
* @param workspaceDeclarationReader the reader on the workspace declaration.
* @param status the tracking status object.
* @return For each line which contains tokens, the line number and the tokens on that line.
*/
private static List<Pair<Integer, List<String>>> lexWorkspaceDeclarationStream(Reader workspaceDeclarationReader, Status status) {
//
// Note: the StreamTokenizer has (among its many problems) a bug where setting the '/' character to a
// wordChar will cause it to fail to recognize slash-slash and slash-star style comments.
// Sun bug id: 4217381 (closed, not fixed..).
//
// This method tries to deal with this problem.
// The stream is tokenized with '/' as a wordChar, but with slashed comments unrecognized.
// Later, the list of tokens is processed again to remove the commented bits.
//
// Use a StreamTokenizer..
StreamTokenizer st = new StreamTokenizer(workspaceDeclarationReader);
// Reset the syntax.. we are creating our own.
st.resetSyntax();
// '!' is the first non-whitespace char..
st.wordChars('!', 127);
st.wordChars(128 + 32, 255); // From the StreamTokenizer constructor. What about 128 to 128 + 31?
// ' ' is the last character which doesn't appear on the console.
st.whitespaceChars(0, ' ');
// Recognize quotes with single- and double-quotes.
st.quoteChar('"');
st.quoteChar('\'');
// Recognize eol's.
st.eolIsSignificant(true);
// List of line # / token pairs.
LinkedList<Pair<Integer, String>> lineNoTokenPairList = new LinkedList<Pair<Integer, String>>();
try {
boolean eof = false;
while (!eof) {
int tokType = st.nextToken();
String token = null;
switch (tokType) {
case StreamTokenizer.TT_WORD :
token = st.sval;
break;
case StreamTokenizer.TT_NUMBER :
// Shouldn't happen, but whatever.. we can handle this anyways.
token = String.valueOf(st.nval);
break;
case '\'':
case '\"':
// Add back quotes to mark as quoted, so that we can properly ignore quoted comment characters.
// We'll strip them back out again in the next phase.
token = '"' + st.sval + '"';
break;
case StreamTokenizer.TT_EOL :
// Ignore..
// Note: st.lineno() reports the next line, not the previous.
break;
case StreamTokenizer.TT_EOF :
eof = true;
break;
default :
token = String.valueOf((char)st.ttype);
break;
}
// If there was a token, add it to the current list of tokens.
// If necessary, also update the current line number, and add the old line to the module location list.
if (token != null) {
int lineNo = st.lineno();
lineNoTokenPairList.add(new Pair<Integer, String>(Integer.valueOf(lineNo), token));
}
}
} catch (IOException ioe) {
// thrown by StreamTokenizer.nextToken();
String errorString = "WorkspaceLoader: IOException reading the workspace file.";
status.add(new Status(Status.Severity.ERROR, errorString, ioe));
return null;
}
//
// Second pass: strip out comments (slash-slash and slash-star).
// This works around a bug where if the slash is set as a word character, slash-slash and slash-star
// comments are not properly stripped.
//
int lineCommentLine = -1; // if >= 0, we're in a line comment for this line.
boolean inBlockComment = false;
// the same list, but without the commented bits.
List<Pair<Integer, String>> lineNoRealTokenPairList = new ArrayList<Pair<Integer, String>>();
while (!lineNoTokenPairList.isEmpty()) {
// Remove the head of the list.
Pair<Integer,String> lineNoTokenPair = lineNoTokenPairList.removeFirst();
int lineNo = lineNoTokenPair.fst().intValue();
String token = lineNoTokenPair.snd();
// Handle the case where we are in a comment.
if (lineCommentLine >= 0) {
if (lineNo == lineCommentLine) {
// Keep consuming tokens on the same line.
continue;
} else {
// A different line: no longer within a line comment.
lineCommentLine = -1;
}
} else if (inBlockComment) {
int endBlockCommentIndex = token.indexOf(END_BLOCK_COMMENT_CHARS);
if (endBlockCommentIndex < 0) {
// The comment does not end.
continue;
}
// The comment ends. Just handle the remnant if any.
inBlockComment = false;
String tokenRemnant = token.substring(endBlockCommentIndex + END_BLOCK_COMMENT_CHARS.length());
if (tokenRemnant.length() == 0) {
continue;
}
// Push the remnant back onto the head of the list.
// Note that we can't just fall out of the loop, since the remnant may itself have comment characters.
lineNoTokenPairList.addFirst(new Pair<Integer, String>(Integer.valueOf(lineNo), tokenRemnant));
continue;
}
// If we are here, we are not currently in a comment.
// Check for quoted string..
if (token.startsWith("\"")) {
// Un-quote the string.
int tokenLength = token.length();
if (token.endsWith("\"") && tokenLength > 1) {
token = token.substring(1, tokenLength - 1);
}
// Note that the empty string "" is passed as an empty token.
} else {
// Determine if the token contains characters to start a comment.
int lineCommentCharsIndex = token.indexOf(LINE_COMMENT_CHARS);
int blockCommentCharsIndex = token.indexOf(BEGIN_BLOCK_COMMENT_CHARS);
// For the case of a comment, retain the bit that isn't part of a comment.
if (lineCommentCharsIndex >= 0 && (blockCommentCharsIndex < 0 || lineCommentCharsIndex < blockCommentCharsIndex)) {
// We're in a line comment.
lineCommentLine = lineNo;
token = token.substring(0, lineCommentCharsIndex);
} else if (blockCommentCharsIndex >= 0) {
// We're in a block comment.
inBlockComment = true;
// Push the remnant back onto the head of the list.
String tokenRemnant = token.substring(blockCommentCharsIndex + BEGIN_BLOCK_COMMENT_CHARS.length());
if (tokenRemnant.length() != 0) {
lineNoTokenPairList.addFirst(new Pair<Integer, String>(Integer.valueOf(lineNo), tokenRemnant));
}
// The token is the bit before the block comment chars.
token = token.substring(0, blockCommentCharsIndex);
}
if (token.length() == 0) {
continue;
}
}
lineNoRealTokenPairList.add(new Pair<Integer, String>(Integer.valueOf(lineNo), token));
}
//
// Now that we have the list of tokens (as line # / token pairs), turn these into lines of tokens.
//
// Line number, Tokens from that line.
List<Pair<Integer, List<String>>> moduleLocationList = new ArrayList<Pair<Integer, List<String>>>();
List<String> currentLineTokens = new ArrayList<String>();
int currentLineNo = -1; // the current line number
// Iterate over the processed list of tokens, populating the module location list
for (final Pair<Integer, String> nextTokenPair : lineNoRealTokenPairList) {
int lineNo = nextTokenPair.fst().intValue();
String token = nextTokenPair.snd();
// Add the token to the current list of tokens.
// If necessary, also update the current line number, and add the old line to the module location list.
if (lineNo != currentLineNo) {
if (currentLineNo > -1) {
moduleLocationList.add(new Pair<Integer, List<String>>(Integer.valueOf(currentLineNo), currentLineTokens));
}
currentLineTokens = new ArrayList<String>();
currentLineNo = lineNo;
}
// Add to the list.
currentLineTokens.add(token);
}
// Add the last line
if (!currentLineTokens.isEmpty()) {
moduleLocationList.add(new Pair<Integer, List<String>>(Integer.valueOf(currentLineNo), currentLineTokens));
}
return moduleLocationList;
}
/**
* Convert a url to a uri.
* @param url the url to convert.
* @return the corresponding uri, or null if the conversion failed.
*/
public static URI urlToUri(URL url) {
if (url == null) {
return null;
}
try {
return new URI(url.getProtocol(), General.decodeUrlUtf8(url.getPath()), null);
} catch (URISyntaxException e) {
CALWorkspace.SERVICES_LOGGER.log(Level.SEVERE, "Error getting uri.", e);
}
return null;
}
/**
* @return the workspace provider factory class.
* The default provider factory will be returned, unless the provider is specified by the relevant system property.
*/
static CALWorkspaceEnvironmentProvider.Factory getWorkspaceProviderFactory() {
String providerFactoryPropertyString = System.getProperty(WorkspaceConfiguration.PROVIDER_FACTORY_PROPERTY);
if (providerFactoryPropertyString != null && providerFactoryPropertyString.length() > 0) {
try {
// Find the class.
Class<?> providerFactoryClass;
try {
providerFactoryClass = ClassInfo.loadClass(providerFactoryPropertyString);
} catch (ClassNotFoundException e) {
String message = "Class \"" + providerFactoryPropertyString + "\", specified as provider factory by the system property, was not found.";
throw new CALWorkspaceEnvironmentProvider.FactoryException(message, e);
}
// Instantiate the class.
try {
return (CALWorkspaceEnvironmentProvider.Factory)providerFactoryClass.newInstance();
} catch (ClassCastException e) {
String message = "Class \"" + providerFactoryPropertyString + "\", specified as provider factory by the system property, is not an instance of a provider factory.";
throw new CALWorkspaceEnvironmentProvider.FactoryException(message, e);
} catch (InstantiationException e) {
String message = "Class \"" + providerFactoryPropertyString + "\", specified as provider factory by the system property, is abstract.";
throw new CALWorkspaceEnvironmentProvider.FactoryException(message, e);
} catch (IllegalAccessException e) {
String message = "Class \"" + providerFactoryPropertyString + "\", specified as provider factory by the system property, is not accessible.";
throw new CALWorkspaceEnvironmentProvider.FactoryException(message, e);
}
} catch (CALWorkspaceEnvironmentProvider.FactoryException e) {
// What to do?
e.printStackTrace();
}
}
// The default provider factory provides BasicCALWorkspaces.
return BasicCALWorkspace.PROVIDER_FACTORY;
}
}