/**
* Copyright (c) 2012 by JP Moresmau
* This code is made available under the terms of the Eclipse Public License,
* version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html
*/
package net.sf.eclipsefp.haskell.buildwrapper.usage;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sf.eclipsefp.haskell.buildwrapper.BWFacade;
import net.sf.eclipsefp.haskell.buildwrapper.BuildWrapperPlugin;
import net.sf.eclipsefp.haskell.buildwrapper.types.Component;
import net.sf.eclipsefp.haskell.buildwrapper.types.Module;
import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineResult;
import net.sf.eclipsefp.haskell.buildwrapper.types.ReferenceLocation;
import net.sf.eclipsefp.haskell.buildwrapper.types.SymbolDef;
import net.sf.eclipsefp.haskell.buildwrapper.types.UsageResults;
import net.sf.eclipsefp.haskell.buildwrapper.util.BWText;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
* The business-oriented API for Usage searches
* @author JP Moresmau
*
*/
public class UsageAPI {
private UsageDB db=new UsageDB();
public void close(){
db.close();
}
public void removeFile(IFile f){
if (f!=null){
try {
db.removeFile(f);
db.commit();
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
}
}
public void removeFile(IProject p, String relPath){
removeFile(p.getFile(relPath));
}
public void addFile(Component c,IFile f){
addFile(f.getProject(),c,f.getProjectRelativePath().toOSString());
}
/**
* add a given file to the db
* @param p the project
* @param c the cabal component
* @param relPath the file project relative path
*/
public void addFile(IProject p,Component c, String relPath){
IFile f=p.getFile(relPath);
if (f!=null && f.exists()){
//BuildWrapperPlugin.log(IStatus.INFO, "Adding "+p.getName()+"/"+f.getProjectRelativePath().toPortableString(), null);
IFile uf=getUsageFile(p, relPath);
// check usage file exists AND has been generated after the source file
if (uf!=null && uf.getLocation().toFile().lastModified()>f.getLocation().toFile().lastModified()){
//BuildWrapperPlugin.log(IStatus.INFO, "Adding "+p.getName()+"/"+f.getProjectRelativePath().toPortableString()+": usage file found", null);
JSONArray arr=parseUsageFile(uf);
if (arr!=null){
try {
String pkg=formatPackage(arr.getString(0));
String module=formatModule(c, arr.getString(1));
String loc=arr.optString(2);
if (module!=null){
long fileID=db.getFileID(f);
db.clearUsageInFile(fileID);
//long moduleID=
db.getModuleID(pkg, module, fileID, loc);
List<ObjectUsage> modUsages=new ArrayList<>();
List<ObjectUsage> objUsages=new ArrayList<>();
List<ObjectUsage> objDefs=new ArrayList<>();
buildUsage(fileID,arr,modUsages,objDefs,objUsages);
db.setModuleUsages(fileID, modUsages);
db.setSymbolDefinitions(fileID, objDefs);
db.setSymbolUsages(fileID, objUsages);
db.commit();
JSONObject outline=arr.optJSONObject(4);
if (outline!=null){
OutlineResult or=new OutlineResult(f, outline);
BWFacade bf=BuildWrapperPlugin.getFacade(p);
if(bf!=null){
bf.registerOutline(f, or);
}
}
}
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.error_parsing_usage_file, je);
}
}
}
}
}
public Map<String,List<ReferenceLocation>> listReferencesInFile(IFile f){
try {
return db.listReferencesInFile(f);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
} catch (JSONException je){
BuildWrapperPlugin.logError(BWText.error_parsing_usage_file, je);
}
return new HashMap<>();
}
private void buildUsage(long fileID,JSONArray arr,List<ObjectUsage> modUsages,List<ObjectUsage> objDefs,List<ObjectUsage> objUsages){
JSONObject pkgs=arr.optJSONObject(3);
if (pkgs!=null){
for (Iterator<String> itPkg=pkgs.keys();itPkg.hasNext();){
String pkgKey=itPkg.next();
JSONObject mods=pkgs.optJSONObject(pkgKey);
if (mods!=null){
String pkg=formatPackage(pkgKey);
buildUsageModule(fileID,pkg,mods,modUsages,objDefs,objUsages);
}
}
}
}
private void buildUsageModule(long fileID,String pkg,JSONObject mods,List<ObjectUsage> modUsages,List<ObjectUsage> objDefs,List<ObjectUsage> objUsages){
for (Iterator<String> itMod=mods.keys();itMod.hasNext();){
String modKey=itMod.next();
JSONObject types=mods.optJSONObject(modKey);
if (types!=null){
try {
long modID=db.getModuleID(pkg, modKey, null,null);
buildUsage(fileID,modID,types.optJSONObject("vars"),false,modUsages,objDefs,objUsages);
buildUsage(fileID,modID,types.optJSONObject("types"),true,modUsages,objDefs,objUsages);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
}
}
}
private void buildUsage(long fileID,long modID,JSONObject symbols,boolean isType,List<ObjectUsage> modUsages,List<ObjectUsage> objDefs,List<ObjectUsage> objUsages) throws SQLException{
if (symbols!=null){
for (Iterator<String> itSym=symbols.keys();itSym.hasNext();){
String symKey=itSym.next();
JSONArray locs=symbols.optJSONArray(symKey);
if (locs!=null){
for (int a=0;a<locs.length();a++){
JSONObject secLoc=locs.optJSONObject(a);
if (secLoc!=null){
String sec=secLoc.optString("s");
JSONArray loc=secLoc.optJSONArray("l");
if (sec!=null && loc!=null){
String sloc=loc.toString();
if (!isType && symKey.length()==0){
modUsages.add(new ObjectUsage(modID, sec,sloc));
} else {
boolean def=secLoc.optBoolean("d", false);
int type=UsageQueryFlags.TYPE_VAR;
if (isType){
type=UsageQueryFlags.TYPE_TYPE;
} else if (Character.isUpperCase(symKey.charAt(0))){
type=UsageQueryFlags.TYPE_CONSTRUCTOR;
}
long symbolID=db.getSymbolID(modID, symKey, type);
if (def){
//, sec, sloc
objDefs.add(new ObjectUsage(symbolID, sec, sloc));
} else {
objUsages.add(new ObjectUsage(symbolID, sec, sloc));
}
}
}
}
}
}
}
}
}
/**
* get the IFile identified with the given db identifier
* @param fileid
* @return
*/
public IFile getFile(Long fileid){
try {
return db.getFile(fileid);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return null;
}
/**
* list all modules defined in the workspace
* @return
*/
public List<Module> listLocalModules() {
try {
return db.listLocalModules();
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new ArrayList<>();
}
/**
* do we have info about this project in the db?
* @param p
* @return
*/
public boolean knowsProject(IProject p){
try {
return db.knowsProject(p.getName());
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return false;
}
/**
* get all references for a module
* @param pkg
* @param module
* @param p
* @param exact
* @return
*/
public UsageResults getModuleReferences(String pkg,String module,IProject p,boolean exact){
try {
return db.getModuleReferences(pkg, module,p,exact);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new UsageResults();
}
/**
* get definitions for a module
* @param pkg
* @param module
* @param p
* @param exact
* @return
*/
public UsageResults getModuleDefinitions(String pkg,String module,IProject p,boolean exact){
try {
return db.getModuleDefinitions(pkg, module,p,exact);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new UsageResults();
}
/**
* get references for a symbol
* @param pkg
* @param module
* @param symbol
* @param type
* @param p
* @param exact
* @return
*/
public UsageResults getSymbolReferences(String pkg,String module,String symbol, int type,IProject p,boolean exact){
try {
return db.getSymbolReferences(pkg, module,symbol,type,p,exact);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new UsageResults();
}
/**
* get definitions for a symbol
* @param pkg
* @param module
* @param symbol
* @param type
* @param p
* @param exact
* @return
*/
public UsageResults getSymbolDefinitions(String pkg,String module,String symbol, int type,IProject p,boolean exact){
try {
return db.getSymbolDefinitions(pkg, module,symbol,type,p,exact);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new UsageResults();
}
/**
* perform an exact search
* @param pkg package restriction (null for everything)
* @param term term to search for
* @param p project restriction (null for everything)
* @param typeFlags @see {@link UsageQueryFlags}
* @param scopeFlags @see {@link UsageQueryFlags}
* @return the results
*/
public UsageResults exactSearch(String pkg,String term,IProject p,int typeFlags,int scopeFlags){
UsageResults ret=new UsageResults();
if ((typeFlags & UsageQueryFlags.TYPE_MODULE) == UsageQueryFlags.TYPE_MODULE){
if ((scopeFlags & UsageQueryFlags.SCOPE_DEFINITIONS) ==UsageQueryFlags.SCOPE_DEFINITIONS){
ret.add(getModuleDefinitions(pkg, term, p,true));
}
if ((scopeFlags & UsageQueryFlags.SCOPE_REFERENCES) ==UsageQueryFlags.SCOPE_REFERENCES){
ret.add(getModuleReferences(pkg, term, p,true));
}
}
int ix=term.lastIndexOf('.');
if (ix>-1 && ix<term.length()-1){
String module=term.substring(0,ix);
String symbol=term.substring(ix+1);
if (Character.isUpperCase(symbol.charAt(0))){
symbolSearch(ret, pkg, module, symbol, UsageQueryFlags.TYPE_TYPE, p, typeFlags, scopeFlags,true);
symbolSearch(ret, pkg, module, symbol, UsageQueryFlags.TYPE_CONSTRUCTOR, p, typeFlags, scopeFlags,true);
} else {
symbolSearch(ret, pkg, module, symbol, UsageQueryFlags.TYPE_VAR, p, typeFlags, scopeFlags,true);
}
}
return ret;
}
/**
* search for a symbol
* @param ret
* @param pkg
* @param module
* @param symbol
* @param type
* @param p
* @param typeFlags
* @param scopeFlags
* @param exact
*/
private void symbolSearch(UsageResults ret,String pkg,String module,String symbol,int type,IProject p,int typeFlags,int scopeFlags,boolean exact){
if ((typeFlags & type) == type){
if ((scopeFlags & UsageQueryFlags.SCOPE_DEFINITIONS) ==UsageQueryFlags.SCOPE_DEFINITIONS){
ret.add(getSymbolDefinitions(pkg, module,symbol,type, p,exact));
}
if ((scopeFlags & UsageQueryFlags.SCOPE_REFERENCES) ==UsageQueryFlags.SCOPE_REFERENCES){
ret.add(getSymbolReferences(pkg, module,symbol,type, p,exact));
}
}
}
/**
* perform an like search
* @param pkg package restriction (null for everything)
* @param term term to search for
* @param p project restriction (null for everything)
* @param typeFlags @see {@link UsageQueryFlags}
* @param scopeFlags @see {@link UsageQueryFlags}
* @return the results
*/
public UsageResults likeSearch(String pkg,String term,IProject p,int typeFlags,int scopeFlags){
UsageResults ret=new UsageResults();
// replace Eclipse wildcards by SQLLite wildcards
StringBuilder newTerm=new StringBuilder();
for (int a=0;a<term.length();a++){
char c=term.charAt(a);
Character prev=a>0?term.charAt(a-1):null;
boolean isEscaped=prev!=null && prev.charValue()=='\\';
if (c=='%'){
newTerm.append("\\%");
} else if (c=='_'){
newTerm.append("\\_");
} else if (c=='*'){
if (isEscaped){
newTerm.replace(newTerm.length()-1, 1, "*");
} else {
newTerm.append("%");
}
} else if (c=='?' && !isEscaped){
if (isEscaped){
newTerm.replace(newTerm.length()-1, 1, "?");
} else {
newTerm.append("_");
}
} else {
newTerm.append(c);
}
}
String sqlLiteTerm=newTerm.toString();
if ((typeFlags & UsageQueryFlags.TYPE_MODULE) == UsageQueryFlags.TYPE_MODULE){
if ((scopeFlags & UsageQueryFlags.SCOPE_DEFINITIONS) ==UsageQueryFlags.SCOPE_DEFINITIONS){
ret.add(getModuleDefinitions(pkg, sqlLiteTerm, p,false));
}
if ((scopeFlags & UsageQueryFlags.SCOPE_REFERENCES) ==UsageQueryFlags.SCOPE_REFERENCES){
ret.add(getModuleReferences(pkg, sqlLiteTerm, p,false));
}
}
if (typeFlags != UsageQueryFlags.TYPE_MODULE){
// there is a dot: qualified name
int ix=sqlLiteTerm.lastIndexOf(".");
List<String> mods=Collections.singletonList(null);
try {
if (ix>-1){
String modTerm=sqlLiteTerm.substring(0,ix);
mods=db.findModules(pkg, modTerm, p);
}
for (String m:mods){
symbolSearch(ret, pkg, m, sqlLiteTerm, UsageQueryFlags.TYPE_TYPE, p, typeFlags, scopeFlags,false);
symbolSearch(ret, pkg, m, sqlLiteTerm, UsageQueryFlags.TYPE_CONSTRUCTOR, p, typeFlags, scopeFlags,false);
symbolSearch(ret, pkg, m, sqlLiteTerm, UsageQueryFlags.TYPE_VAR, p, typeFlags, scopeFlags,false);
}
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
}
return ret;
}
public List<SymbolDef> listDefinedSymbols(IProject p){
try {
return db.listDefinedSymbols(p);
} catch (SQLException sqle){
BuildWrapperPlugin.logError(BWText.error_db, sqle);
}
return new ArrayList<>();
}
/**
* parse the file generated by BuildWrapper
* @param uf
* @return
*/
private JSONArray parseUsageFile(IFile uf){
try (InputStream is=uf.getContents();
BufferedReader br = new BufferedReader(new InputStreamReader( is,uf.getCharset() ))) {
return new JSONArray(new JSONTokener(br));
} catch (Exception e){
BuildWrapperPlugin.logError(BWText.error_parsing_usage_file, e);
}
return null;
}
/**
* get the usage file generated by BuildWrapper
* @param p
* @param relPath
* @return
*/
private IFile getUsageFile(IProject p, String relPath){
IFolder fldr=p.getFolder(BWFacade.DIST_FOLDER);
if (fldr!=null && fldr.exists()){
IFile f=fldr.getFile(relPath);
IFile f2=((IFolder)f.getParent()).getFile("."+f.getName()+".bwusage");
if (f2.exists()){
return f2;
}
}
return null;
}
/**
* get the package in the proper format (we drop the version)
* @param pkg
* @return
*/
private String formatPackage(String pkg){
String ret=pkg;
int ix=pkg.lastIndexOf('-');
if (ix>-1 && ix<pkg.length()-1){
if (Character.isDigit(pkg.charAt(ix+1))){
ret=pkg.substring(0,ix);
}
}
return ret;
}
/**
* get the module in the proper format (Main modules get postfixed by the cabal component name to distinguish them)
* @param c
* @param module
* @return
*/
private String formatModule(Component c,String module){
String ret=module;
if("Main".equals(module)){
if (c.getName()==null){ // Main but a library, ignore
return null;
}
ret=module+" "+c.getName();
}
return ret;
}
}