/*******************************************************************************
* Copyright © 2008, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.ide.core.internal.lookup;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.edt.compiler.binding.ITypeBinding;
import org.eclipse.edt.compiler.core.ast.DataItem;
import org.eclipse.edt.compiler.core.ast.DefaultASTVisitor;
import org.eclipse.edt.compiler.core.ast.Delegate;
import org.eclipse.edt.compiler.core.ast.Class;
import org.eclipse.edt.compiler.core.ast.Enumeration;
import org.eclipse.edt.compiler.core.ast.ExternalType;
import org.eclipse.edt.compiler.core.ast.File;
import org.eclipse.edt.compiler.core.ast.Handler;
import org.eclipse.edt.compiler.core.ast.ImportDeclaration;
import org.eclipse.edt.compiler.core.ast.Interface;
import org.eclipse.edt.compiler.core.ast.Library;
import org.eclipse.edt.compiler.core.ast.PackageDeclaration;
import org.eclipse.edt.compiler.core.ast.Part;
import org.eclipse.edt.compiler.core.ast.Program;
import org.eclipse.edt.compiler.core.ast.Record;
import org.eclipse.edt.compiler.core.ast.Service;
import org.eclipse.edt.compiler.internal.core.builder.BuildException;
import org.eclipse.edt.compiler.internal.core.builder.IProblemRequestor;
import org.eclipse.edt.ide.core.internal.utils.Util;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
public abstract class AbstractFileInfoCreator {
protected class ASTPartSourceReader {
private Reader stringReader;
private int currentOffset;
public ASTPartSourceReader(Reader stringReader){
this.stringReader = stringReader;
}
public void seekToElement(int elementOffset) throws IOException{
if(currentOffset > elementOffset){
throw new BuildException("Error seeking to element"); //$NON-NLS-1$
}
while(currentOffset != elementOffset){
read();
}
}
public int read() throws IOException{
currentOffset += 1;
return stringReader.read();
}
public void close() throws IOException{
stringReader.close();
}
}
protected AbstractProjectInfo projectInfo;
protected String packageName;
protected IFile file;
protected IDuplicatePartRequestor duplicatePartRequestor;
protected ASTFileInfo result = new ASTFileInfo();;
protected HashSet addedNames = new HashSet();
private File fileAST;
private boolean hasGeneratablePart = false;
private MessageDigest messageDigest;
private List errors = new ArrayList();
public AbstractFileInfoCreator(AbstractProjectInfo projectInfo, String packageName, IFile file, File fileAST, IDuplicatePartRequestor duplicatePartRequestor){
this.projectInfo = projectInfo;
this.packageName = packageName;
this.file = file;
this.fileAST = fileAST;
this.duplicatePartRequestor = duplicatePartRequestor;
try {
messageDigest = MessageDigest.getInstance("MD5"); //$NON-NLS-1$
} catch (NoSuchAlgorithmException e) {
throw new BuildException("Error creating AST FileInfo", e); //$NON-NLS-1$
}
}
protected abstract String getContents() throws CoreException, IOException;
private void initializeASTInfo() {
try{
initializeLineNumbers();
initializeParts();
}catch(IOException e){
throw new BuildException("Error creating AST FileInfo", e); //$NON-NLS-1$
}catch(CoreException e){
throw new BuildException("Error creating AST FileInfo", e); //$NON-NLS-1$
} catch (BadLocationException e) {
throw new BuildException("Error creating AST FileInfo", e); //$NON-NLS-1$
}
}
private void initializeLineNumbers() throws CoreException, IOException, BadLocationException {
DefaultLineTracker lineTracker = new DefaultLineTracker();
lineTracker.set(getContents());
int numberOfLines = lineTracker.getNumberOfLines();
int[] offsets = new int[numberOfLines];
for(int i=0; i<numberOfLines; i++){
offsets[i] = lineTracker.getLineOffset(i);
}
result.setLineOffsets(offsets);
}
private void initializeParts() throws CoreException, IOException {
final ASTPartSourceReader reader = new ASTPartSourceReader(new BufferedReader(new StringReader(getContents())));
try{
fileAST.accept(new DefaultASTVisitor(){
public boolean visit(File fileNode){
int headerStartOffset = 0;
int headerEndOffset = 0;
PackageDeclaration packageDeclaration = fileNode.getPackageDeclaration();
List importDeclarations = fileNode.getImportDeclarations();
if(packageDeclaration != null){
headerStartOffset = packageDeclaration.getOffset();
result.setCaseSensitivePackageName(packageDeclaration.getName().getCanonicalName());
}else {
result.setCaseSensitivePackageName("");
if(importDeclarations.size() > 0){
headerStartOffset = ((ImportDeclaration)importDeclarations.get(0)).getOffset();
}else{
headerStartOffset = 0;
}
}
if(importDeclarations.size() > 0){
ImportDeclaration lastDecl = (ImportDeclaration)importDeclarations.get(importDeclarations.size() - 1);
headerEndOffset = (lastDecl.getOffset() + lastDecl.getLength());
}else if(packageDeclaration != null){
headerEndOffset = packageDeclaration.getOffset() + packageDeclaration.getLength();
}else{
headerEndOffset = 0;
}
processFilePart(reader, Util.getFilePartName(file), Util.getCaseSensitiveFilePartName(file), ITypeBinding.FILE_BINDING, headerStartOffset, (headerEndOffset - headerStartOffset));
return true;
}
public boolean visit(Handler handler){
processPart(reader, handler, ITypeBinding.HANDLER_BINDING, handler.getOffset(), handler.getLength());
return false;
}
public boolean visit(DataItem dataItem) {
processPart(reader, dataItem, ITypeBinding.DATAITEM_BINDING, dataItem.getOffset(), dataItem.getLength());
return false;
}
public boolean visit(Interface interfaceNode) {
processPart(reader, interfaceNode, ITypeBinding.INTERFACE_BINDING, interfaceNode.getOffset(), interfaceNode.getLength());
return false;
}
public boolean visit(Library library) {
processPart(reader, library, ITypeBinding.LIBRARY_BINDING, library.getOffset(), library.getLength());
return false;
}
public boolean visit(Program program) {
processPart(reader, program, ITypeBinding.PROGRAM_BINDING, program.getOffset(), program.getLength());
return false;
}
public boolean visit(Class eglClass) {
processPart(reader, eglClass, ITypeBinding.CLASS_BINDING, eglClass.getOffset(), eglClass.getLength());
return false;
}
public boolean visit(Record record) {
processPart(reader, record, ITypeBinding.FLEXIBLE_RECORD_BINDING, record.getOffset(), record.getLength());
return false;
}
public boolean visit(Service service) {
processPart(reader, service, ITypeBinding.SERVICE_BINDING, service.getOffset(), service.getLength());
return false;
}
public boolean visit(Enumeration enumeration) {
processPart(reader, enumeration, ITypeBinding.ENUMERATION_BINDING, enumeration.getOffset(), enumeration.getLength());
return false;
}
public boolean visit(ExternalType externalType) {
processPart(reader, externalType, ITypeBinding.EXTERNALTYPE_BINDING, externalType.getOffset(), externalType.getLength());
return false;
}
public boolean visit(Delegate delegate) {
processPart(reader, delegate, ITypeBinding.DELEGATE_BINDING, delegate.getOffset(), delegate.getLength());
return false;
}
});
}finally{
reader.close();
}
result.setErrors(errors);
}
private void processFilePart(ASTPartSourceReader reader, String caseInsensitiveFileName, String caseSensitiveFileName, int partType, int sourceStart, int sourceLength){
if (checkForDuplicates()){
if(!isDuplicatePart(caseInsensitiveFileName)){
addPart(reader, caseInsensitiveFileName, caseSensitiveFileName, partType, sourceStart, sourceLength);
}else{
duplicatePartRequestor.acceptDuplicatePart(caseInsensitiveFileName);
errors.add(new FileInfoError(sourceStart, sourceLength, IProblemRequestor.DUPLICATE_NAME_IN_NAMESPACE, new String[]{caseInsensitiveFileName}));
}
}else{
addPart(reader, caseInsensitiveFileName, caseSensitiveFileName, partType, sourceStart, sourceLength);
}
}
private void processPart(ASTPartSourceReader reader, Part part, int partType, int sourceStart, int sourceLength) {
if(partRequiresOnePerFile(part)){
validateGeneratablePart(part);
}
String caseInsensitivePartName = part.getIdentifier();
if (checkForDuplicates()){
if(!isDuplicatePart(caseInsensitivePartName)){
addPart(reader, caseInsensitivePartName, part.getName().getCaseSensitiveIdentifier(), partType, sourceStart, sourceLength);
}else{
duplicatePartRequestor.acceptDuplicatePart(caseInsensitivePartName);
errors.add(new FileInfoError(part.getName().getOffset(), part.getName().getLength(), IProblemRequestor.DUPLICATE_NAME_IN_NAMESPACE, new String[]{caseInsensitivePartName}));
}
}else{
addPart(reader, caseInsensitivePartName, part.getName().getCaseSensitiveIdentifier(), partType, sourceStart, sourceLength);
}
}
private void addPart(ASTPartSourceReader reader, String caseInsensitivePartName, String caseSensitivePartName, int partType, int sourceStart, int sourceLength) {
addedNames.add(caseInsensitivePartName);
try{
result.addPart(caseInsensitivePartName, partType, sourceStart, sourceLength, caseSensitivePartName, calculateMD5Key(reader, sourceStart, sourceLength));
}catch(IOException e){
throw new BuildException("Error creating AST FileInfo", e); //$NON-NLS-1$
}
}
protected boolean checkForDuplicates(){
return true;
}
protected boolean isDuplicatePart(String caseInsensitivePartName) {
boolean duplicate = false;
IFile declaringFile = projectInfo.getPartOrigin(packageName, caseInsensitivePartName).getEGLFile();
if(declaringFile != null && !declaringFile.equals(file)){
// duplicate part in different file
duplicate = true;
}else if(addedNames.contains(caseInsensitivePartName)){
// duplicate part in same file
duplicate = true;
}else{
duplicate = false;
}
return duplicate;
}
private void validateGeneratablePart(Part part) {
if(hasGeneratablePart == true){
String fileName = file.getFullPath().removeFileExtension().lastSegment();
errors.add(new FileInfoError(part.getName().getOffset(), part.getName().getLength(), IProblemRequestor.ONLY_ONE_GENERATABLE_PART_PER_FILE, new String[]{part.getPartTypeName(), part.getName().getCanonicalName(), fileName}));
}else{
String fileName = file.getFullPath().removeFileExtension().lastSegment();
hasGeneratablePart = true;
if(!part.getName().getCanonicalName().equals(fileName)){
errors.add(new FileInfoError(part.getName().getOffset(), part.getName().getLength(), IProblemRequestor.GENERATABLE_PART_NAME_MUST_MATCH_FILE_NAME, new String[]{part.getPartTypeName(), part.getName().getCanonicalName(), fileName}));
}
}
}
private byte[] calculateMD5Key(ASTPartSourceReader reader, int offset, int length) throws IOException {
byte[] partBytes = new byte[length];
reader.seekToElement(offset);
for(int i=0; i< length; i++){
partBytes[i] = (byte)reader.read();
}
return messageDigest.digest(partBytes);
}
public IASTFileInfo getASTInfo() {
initializeASTInfo();
return result;
}
private boolean partRequiresOnePerFile(Part part) {
switch (part.getPartType()) {
case Part.PROGRAM:
case Part.LIBRARY:
case Part.CLASS:
case Part.HANDLER:
case Part.SERVICE:
return true;
default:
return false;
}
}
}