/**
* Copyright (c) 2010, B. Scott Michel (bscottm@ieee.org)
*
* 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.core.cabal;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.sf.eclipsefp.haskell.core.HaskellCorePlugin;
import net.sf.eclipsefp.haskell.core.internal.util.CoreTexts;
import net.sf.eclipsefp.haskell.core.preferences.ICorePreferenceNames;
import net.sf.eclipsefp.haskell.util.FileUtil;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Version;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
/** The container for managing Cabal implementations. This container also knows how to
* serialize and de-serialize preferences as XML.
*
* @author B. Scott Michel
*
*/
public class CabalImplementationManager {
/** Number of instances serialized in preferences */
public static final String NUM_INSTANCES = "numImpls"; //$NON-NLS-1$
/** The default Cabal implementation's identifier preference */
public static final String DEFAULT_CABAL_IMPLEMENTATION = "defaultImpl"; //$NON-NLS-1$
/** Implementation's identifier preference node */
private static final String ATT_NAME = "name"; //$NON-NLS-1$
/** XML attribute within {@link #ELEM_CABAL_IMPL} for the implementation's executable path */
private static final String ATT_EXECUTABLE = "executable"; //$NON-NLS-1$
/** XML attribute within {@link #ELEM_CABAL_IMPL} for the implementation's cabal-install version */
private static final String ATT_INSTALL_VERSION = "installVersion"; //$NON-NLS-1$
/** XML attribute within {@link #ELEM_CABAL_IMPL} for the implementation's Cabal library version */
private static final String ATT_LIBRARY_VERSION = "libraryVersion"; //$NON-NLS-1$
/** The array of Cabal implementations */
private List<CabalImplementation> impls;
/** The default implementation */
private CabalImplementation defaultImpl;
/** Singleton instance holder */
private static class CabalImplsSingletonHolder {
final static CabalImplementationManager theInstance = new CabalImplementationManager();
}
/** The default constructor, private to ensure that it remains a singleton instance. */
private CabalImplementationManager() {
// Load the implementations from the preference store:
impls = new ArrayList<>();
defaultImpl = null;
deserializePrefs();
}
/** Get the singleton instance */
public static CabalImplementationManager getInstance() {
return CabalImplsSingletonHolder.theInstance;
}
/** Get the default Cabal implementation */
public final CabalImplementation getDefaultCabalImplementation() {
return defaultImpl;
}
public final CabalImplementation findImplementation( final String ident ) {
CabalImplementation retval = null;
for (CabalImplementation impl : impls) {
if (impl.getUserIdentifier().equals( ident )) {
retval = impl;
break;
}
}
return retval;
}
/** Get the implementations array, generally done when the UI's preference
* pane is starting.
*/
public List<CabalImplementation> getCabalImplementations() {
return impls;
}
/** Set the implementations array, generally done when the UI's preference
* pane has updated data.
*
* @param impls The newly updated implementations list
*/
public void setCabalImplementations( final List<CabalImplementation> impls, final String defaultImplIdent ) {
this.impls = impls;
this.defaultImpl = findImplementation( defaultImplIdent );
serializePrefs( defaultImplIdent );
}
public void serializePrefs( final String defaultImplIdent ) {
IEclipsePreferences instanceNode = HaskellCorePlugin.instanceScopedPreferences();
try {
if (instanceNode.nodeExists( ICorePreferenceNames.CABAL_IMPLEMENTATIONS )) {
instanceNode.node( ICorePreferenceNames.CABAL_IMPLEMENTATIONS).removeNode();
instanceNode.remove( ICorePreferenceNames.CABAL_IMPLEMENTATIONS );
instanceNode.flush();
}
Preferences node = instanceNode.node( ICorePreferenceNames.CABAL_IMPLEMENTATIONS );
node.putInt( NUM_INSTANCES, impls.size() );
int seqno = 1;
for (CabalImplementation impl : impls) {
Preferences implSeqNode = node.node( String.valueOf( seqno ) );
serializePrefs( implSeqNode, impl );
++seqno;
}
node.put( DEFAULT_CABAL_IMPLEMENTATION, defaultImplIdent );
node.sync();
instanceNode.sync();
} catch (BackingStoreException ex) {
HaskellCorePlugin.log( ex.toString(), ex );
try {
// Remove the preferences.
instanceNode.node(ICorePreferenceNames.CABAL_IMPLEMENTATIONS).removeNode();
instanceNode.remove( ICorePreferenceNames.CABAL_IMPLEMENTATIONS );
instanceNode.flush();
} catch (BackingStoreException ex2) {
// Ignore it.
}
}
}
private static void serializePrefs( final Preferences node, final CabalImplementation impl ) {
node.put( ATT_NAME, impl.getUserIdentifier() );
node.put( ATT_EXECUTABLE, impl.getCabalExecutableName().toPortableString());
node.put( ATT_INSTALL_VERSION, impl.getInstallVersion() );
node.put( ATT_LIBRARY_VERSION, impl.getLibraryVersion() );
}
public void deserializePrefs () {
IEclipsePreferences instanceNode = HaskellCorePlugin.instanceScopedPreferences();
try {
if (instanceNode.nodeExists( ICorePreferenceNames.CABAL_IMPLEMENTATIONS )) {
Preferences node = instanceNode.node( ICorePreferenceNames.CABAL_IMPLEMENTATIONS );
int numImpls = node.getInt( NUM_INSTANCES, 0);
String defaultImplIdent = node.get( DEFAULT_CABAL_IMPLEMENTATION, new String() );
boolean shouldSave=false;
for (int i = 1; i <= numImpls; ++i) {
String implSeqString = String.valueOf(i);
if (node.nodeExists( implSeqString )) {
Preferences implNode = node.node( implSeqString );
String ident = implNode.get( ATT_NAME, null );
String exePath = implNode.get( ATT_EXECUTABLE, null);
String installVersion = implNode.get(ATT_INSTALL_VERSION, null);
String libraryVersion = implNode.get( ATT_LIBRARY_VERSION, null );
if ( ident != null
&& exePath != null
&& installVersion != null
&& libraryVersion != null) {
IPath path= Path.fromPortableString( exePath );
CabalImplementation impl = new CabalImplementation(ident,path);
File f=new File(path.toOSString());
if (!f.exists()){
shouldSave=true;
HaskellCorePlugin.log( NLS.bind( CoreTexts.cabalexe_notfound, f.getAbsolutePath() ), IStatus.ERROR );
} else {
impls.add(impl);
if (!installVersion.equals(impl.getInstallVersion())){
shouldSave=true;
HaskellCorePlugin.log( NLS.bind( CoreTexts.cabalversion_mismatch_install, installVersion,impl.getInstallVersion() ), IStatus.WARNING );
}
if (!libraryVersion.equals(impl.getLibraryVersion())){
shouldSave=true;
HaskellCorePlugin.log( NLS.bind( CoreTexts.cabalversion_mismatch_library, libraryVersion,impl.getLibraryVersion() ), IStatus.WARNING );
}
}
} else {
String diagnostic = "Invalid cabal implementation (".concat( String.valueOf(i) ).concat( "), missing items: "); //$NON-NLS-1$ //$NON-NLS-2$
int diagLen = diagnostic.length();
ArrayList<String> diags = new ArrayList<>();
if (ident == null) {
diags.add("identifier"); //$NON-NLS-1$
}
if (exePath == null) {
diags.add("executable path"); //$NON-NLS-1$
}
if (installVersion == null) {
diags.add("install version"); //$NON-NLS-1$
}
if (libraryVersion == null) {
diags.add("library version"); //$NON-NLS-1$
}
for (String diag : diags) {
if (diagnostic.length() > diagLen) {
diagnostic.concat( ", " ); //$NON-NLS-1$
}
diagnostic.concat( diag );
}
HaskellCorePlugin.log( diagnostic, IStatus.ERROR );
}
}
}
defaultImpl = findImplementation( defaultImplIdent );
if (shouldSave){
serializePrefs( defaultImplIdent);
}
}
if (impls.size()==0){
List<CabalImplementation> detectedImpls=autoDetectCabalImpls();
if (detectedImpls.size()>0){
setCabalImplementations( detectedImpls, detectedImpls.get(0).getUserIdentifier() );
}
}
} catch (BackingStoreException ex) {
// nothing.
}
}
public static List<CabalImplementation> autoDetectCabalImpls() {
ArrayList<File> candidateLocs = FileUtil.getCandidateLocations();
List<CabalImplementation> impls=new ArrayList<>();
Set<String> paths=new HashSet<>();
for (File loc : candidateLocs) {
File[] files = loc.listFiles( new FilenameFilter() {
@Override
public boolean accept( final File dir, final String name ) {
// Catch anything starting with "cabal", because MacPorts (and others) may install
// "cabal-1.8.0" as a legitimate cabal executable.
return name.startsWith( CabalImplementation.CABAL_BASENAME );
}
});
if (files != null && files.length > 0) {
for (File file : files) {
try {
String path=file.getCanonicalPath();
if( paths.add( path )){
CabalImplementation impl = new CabalImplementation("foo", new Path(path)); //$NON-NLS-1$
// if we can't get a version, it's not a valid Cabal install
if (impl.getInstallVersion()!=null && impl.getInstallVersion().length()>0){
int seqno = 1;
String ident = CabalImplementation.CABAL_BASENAME.concat( "-" ).concat(impl.getInstallVersion()); //$NON-NLS-1$
if (!isUniqueUserIdentifier( ident, impls )) {
String uniqIdent = ident.concat( "-" ).concat( String.valueOf( seqno ) ); //$NON-NLS-1$
while (!isUniqueUserIdentifier(uniqIdent, impls)) {
seqno++;
uniqIdent = ident.concat( "-" ).concat( String.valueOf( seqno ) ); //$NON-NLS-1$
}
ident = uniqIdent;
}
impl.setUserIdentifier( ident );
impls.add( impl );
}
}
} catch (IOException e) {
// Ignore?
}
}
}
}
return impls;
}
public static boolean isUniqueUserIdentifier (final String ident,final List<CabalImplementation> impls) {
boolean retval = true;
for (CabalImplementation impl : impls) {
if (impl.getUserIdentifier().equals(ident)) {
retval = false;
break;
}
}
return retval;
}
public Preferences cabalImplementationsPreferenceNode() {
IEclipsePreferences instanceNode = HaskellCorePlugin.instanceScopedPreferences();
return instanceNode.node( ICorePreferenceNames.CABAL_IMPLEMENTATIONS );
}
public static String getCabalExecutable(){
CabalImplementationManager cabalMgr = CabalImplementationManager.getInstance();
CabalImplementation cabalImpl = cabalMgr.getDefaultCabalImplementation();
if (cabalImpl!=null){
final String cabalExecutable = cabalImpl.getCabalExecutableName().toOSString();
if (cabalExecutable==null || cabalExecutable.length()<1){
HaskellCorePlugin.log( CoreTexts.zerolenCabalExecutable_message,IStatus.ERROR);
return null;
}
return cabalExecutable;
}
return null;
}
public static Version getCabalLibraryVersion(){
CabalImplementationManager cabalMgr = CabalImplementationManager.getInstance();
CabalImplementation cabalImpl = cabalMgr.getDefaultCabalImplementation();
if (cabalImpl!=null){
final String cabalVersion=cabalImpl.getLibraryVersion();
if (cabalVersion!=null && cabalVersion.length()>0){
return new Version(cabalVersion);
}
}
return null;
}
}