/*******************************************************************************
* Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) 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:
* Thomas Holland - initial API and implementation
*******************************************************************************/
package de.innot.avreclipse.core.toolinfo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.Bundle;
import de.innot.avreclipse.AVRPlugin;
import de.innot.avreclipse.core.IMCUProvider;
/**
* This class handles the list of known MCU signatures.
* <p>
* Each AVR MCU is identified by a 3-byte signature. This class handles the mappings between the MCU
* id and its signature.
* </p>
* <p>
* <ul>
* <li>To get the Signature for a known MCU Id use {@link #getSignature(String)}</li>
* <li>To get the MCU ID for a known Signature use {@link #getMCU(String)}</li>
* </ul>
* </p>
* <p>
* The signatures are stored as Strings with a format of "0x123456" (C style hex format)
* </p>
* <p>
* This class loads a default list of signatures from the signatures.properties file, which is
* located in the properties folder of the core plugin. Additional / overriding signatures can be
* added with the {@link #addSignature(String, String)} method. With a call to
* {@link #storeSignatures()} these additional signatures are persisted in the instance state area (<code>.metadata/.plugins/de.innot.avreclipse.core/signatures.properties</core>)
* and reloaded at the next start.
* </p>
* @author Thomas Holland
* @since 2.2
*
*/
public class Signatures implements IMCUProvider {
// paths to the default and instance properties files
private final static IPath DEFAULTPROPSFILE = new Path("properties/signature.properties");
private final static IPath INSTANCEPROPSFILE = new Path("signatures.properties");
// properties are stored as key=mcuid, value=signature
private Properties fProps = new Properties();
private static Signatures fInstance = null;
/**
* Get the default instance of the Signatures class
*/
public static Signatures getDefault() {
if (fInstance == null)
fInstance = new Signatures();
return fInstance;
}
// private constructor to prevent instantiation
private Signatures() {
// The constructor will first read the default signatures from
// the plugin signature.properties file.
// Then it tries to load an existing instance signature property file.
// Load the list of signatures from the signature.properties file
// as the default values.
Properties mcuDefaultProps = new Properties();
Bundle avrplugin = AVRPlugin.getDefault().getBundle();
InputStream is = null;
try {
is = FileLocator.openStream(avrplugin, DEFAULTPROPSFILE, false);
mcuDefaultProps.load(is);
is.close();
} catch (IOException e) {
// this should not happen because the signatures.properties is
// part of the plugin and always there.
AVRPlugin.getDefault().log(
new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't find signatures.properties", e));
return;
}
// Load any instance signatures from the plugin state location
fProps = new Properties(mcuDefaultProps);
File propsfile = getInstanceSignatureProperties();
if (propsfile.canRead()) {
try {
is = new FileInputStream(propsfile);
fProps.load(is);
is.close();
} catch (IOException e) {
AVRPlugin.getDefault().log(
new Status(Status.ERROR, AVRPlugin.PLUGIN_ID,
"Can't read instance signatures.properties", e));
// continue anyway without the instance signatures
}
}
}
/**
* Get the Signature for the given MCU id.
*
* @param mcuid
* String with a MCU id
* @return String with the MCU signature in hex ("0x123456") or <code>null</code> if the given
* MCU id is unknown.
*/
public String getSignature(String mcuid) {
return fProps.getProperty(mcuid);
}
/**
* Get the MCU id for the given Signature.
* <p>
* If multiple MCUs share the same signature (e.g. ATmega169 and ATmega169p), then the one with the shortest name is returned.
*
* @param signature
* String with a signature in hex ("0x123456")
* @return String with the corresponding MCU id or * <code>null</code> if the given signature
* is unknown.
*/
public String getMCU(String signature) {
// iterate over all mcuids to find the one with the given signature
// I do not use a reverse lookup map because this method will not be
// called often and a reverse map would add code complexity.
// However there is a problem
Enumeration<?> keyset = fProps.propertyNames();
List<String> allMCUs = new ArrayList<String>();
while (keyset.hasMoreElements()) {
Object mcukey = keyset.nextElement();
if (mcukey != null && mcukey instanceof String) {
String mcuid = (String) mcukey;
if (fProps.getProperty(mcuid).equalsIgnoreCase(signature)) {
allMCUs.add(mcuid);
}
}
}
if (allMCUs.size() == 0) {
// No matching MCU found
return null;
}
if (allMCUs.size() == 1) {
// Only one found
return allMCUs.get(0);
}
// more than one MCU matched the signature.
// find the one with the shortest name and return it.
String bestmcu = null;
int bestmculength = 99;
for (String currmcu : allMCUs) {
if (currmcu.length() < bestmculength) {
bestmcu = currmcu;
bestmculength = currmcu.length();
}
}
return bestmcu;
}
/**
* Add a MCU signature to the list.
* <p>
* The signature is only added if it differs from the default signature.
* </p>
*
* @param mcuid
* String with a MCU id
* @param signature
* String with the signature in format "0x123456"
*/
public void addSignature(String mcuid, String signature) {
Assert.isNotNull(mcuid);
Assert.isNotNull(signature);
String oldsig = fProps.getProperty(mcuid);
if (!signature.equalsIgnoreCase(oldsig)) {
fProps.setProperty(mcuid, signature);
}
}
/**
* Stores the signature properties in the Eclipse instance storage area.
* <p>
* The generated properties file only contains additional signatures not in the default list.
* </p>
* <p>
*
* @throws IOException
* for any error writing the properties file
*/
public void storeSignatures() throws IOException {
File propsfile = getInstanceSignatureProperties();
FileOutputStream os = null;
try {
os = new FileOutputStream(propsfile);
fProps.store(os, "Additional MCU Signature values");
} finally {
// close the stream if the fProps.store() method failed
if (os != null) {
os.close();
}
}
}
//
// Methods of the IMCUProvider Interface
//
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.core.IMCUProvider#getMCUInfo(java.lang.String)
*/
public String getMCUInfo(String mcuid) {
return fProps.getProperty(mcuid);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.core.IMCUProvider#getMCUList()
*/
public Set<String> getMCUList() {
// Return all keys of the underlying properties (the mcuids)
// as a List
// I used "fProps.stringPropertyNames() first, but that is a JDK 1.6 method and we still
// want to run on 1.5
Enumeration<?> keyset = fProps.propertyNames();
Set<String> mcuset = new HashSet<String>();
while (keyset.hasMoreElements()) {
Object name = keyset.nextElement();
if (name != null && name instanceof String) {
mcuset.add((String) name);
}
}
return mcuset;
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.core.IMCUProvider#hasMCU(java.lang.String)
*/
public boolean hasMCU(String mcuid) {
String sig = fProps.getProperty(mcuid);
return sig != null ? true : false;
}
/**
* @return File pointing to the instance signature properties file
*/
private File getInstanceSignatureProperties() {
IPath propslocation = AVRPlugin.getDefault().getStateLocation().append(INSTANCEPROPSFILE);
return propslocation.toFile();
}
}