/*******************************************************************************
* 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:
* Manuel Stahl - initial API and implementation
* Thomas Holland - rewritten and improved
*******************************************************************************/
package de.innot.avreclipse.devicedescription.avrio;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import de.innot.avreclipse.core.paths.AVRPath;
import de.innot.avreclipse.core.paths.AVRPathProvider;
import de.innot.avreclipse.core.paths.IPathProvider;
import de.innot.avreclipse.core.preferences.AVRPathsPreferences;
import de.innot.avreclipse.core.util.AVRMCUidConverter;
import de.innot.avreclipse.devicedescription.ICategory;
import de.innot.avreclipse.devicedescription.IDeviceDescription;
import de.innot.avreclipse.devicedescription.IDeviceDescriptionProvider;
import de.innot.avreclipse.devicedescription.IEntry;
import de.innot.avreclipse.devicedescription.IProviderChangeListener;
/**
* Provides DeviceDescription Objects based on parsing the <avr/io.h> file.
* <p>
* As the information in the include/avr folder is static, the class should be accessed with the
* static method {@link #getDefault()}.
* </p>
* <p>
* <b>Note:</b> The Registers defined in <avr/io.h>, namely the SREG and SP(L|H) are not included,
* as parsing io.h would require an understanding of <code>#ifdef</code>, which the simple parser
* in this class has not.
* </p>
*
* @author Thomas Holland
* @author Manuel Stahl
*/
public class AVRiohDeviceDescriptionProvider implements IDeviceDescriptionProvider,
IPropertyChangeListener {
private static AVRiohDeviceDescriptionProvider instance = null;
private Map<String, String> fMCUNamesMap = null;
private Map<String, DeviceDescription> fCache = null;
private String fInternalErrorMsg = null;
private final List<IProviderChangeListener> fChangeListeners = new ArrayList<IProviderChangeListener>(
0);
private final IPathProvider fPathProvider = new AVRPathProvider(
AVRPath.AVRINCLUDE);
/**
* Get an instance of this DeviceModelProvider.
*/
public static AVRiohDeviceDescriptionProvider getDefault() {
if (instance == null)
instance = new AVRiohDeviceDescriptionProvider();
return instance;
}
/**
* private default constructor, so the class can only be accessed via the singleton getDefault()
* method.
*
* The Constructor will register a Preference change listener to be informed about any changes
* to the <avr/io.h> path preference value.
*/
private AVRiohDeviceDescriptionProvider() {
// Add ourself as a listener to Path Preference change events
IPreferenceStore store = AVRPathsPreferences.getPreferenceStore();
store.addPropertyChangeListener(this);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.devicedescription.IDeviceDescriptionProvider#getDeviceList()
*/
public Set<String> getMCUList() {
if (fMCUNamesMap == null) {
try {
loadDevices();
} catch (IOException ioe) {
return null;
}
}
Set<String> devs = new HashSet<String>(fMCUNamesMap.keySet());
return devs;
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.core.IMCUProvider#hasMCU(java.lang.String)
*/
public boolean hasMCU(String mcuid) {
Set<String> mcus = getMCUList();
if (mcus != null) {
return getMCUList().contains(mcuid);
}
return false;
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.devicedescription.IDeviceDescriptionProvider#getDevice(java.lang.String)
*/
public String getMCUInfo(String name) {
IDeviceDescription dd = getDeviceDescription(name);
if (dd == null) {
return null;
}
List<String> headerfiles = dd.getSourcesList();
if (headerfiles.size() > 0) {
// TODO: maybe append the sub-header files
return headerfiles.get(0);
}
return null;
}
/**
* Returns the IDeviceDescription for the given MCU id
*
* @param name
* String with a MCU id
* @return <code>IDeviceDescription</code> or <code>null</code> if the give MCU id is not
* known or an error has occured reading the files.
*/
public IDeviceDescription getDeviceDescription(String name) {
if (name == null)
return null;
if (fMCUNamesMap == null) {
try {
loadDevices();
} catch (IOException ioe) {
// return null on errors
return null;
}
}
// check if the name actually exists (and has a headerfile to load its
// properties from)
String headerfile = fMCUNamesMap.get(name);
if (headerfile == null)
return null;
// Test if we already have this device in the cache
if (fCache == null) {
fCache = new HashMap<String, DeviceDescription>();
}
DeviceDescription currdev = fCache.get(name);
if (currdev == null) {
// No: create a new DeviceDescription
currdev = new DeviceDescription(name);
try {
readDeviceHeader(currdev, headerfile);
} catch (IOException e) {
return null;
}
// Add the DeviceDescription to the cache
fCache.put(name, currdev);
}
return currdev;
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.devicedescription.IDeviceDescriptionProvider#getBasePath()
*/
public IPath getBasePath() {
return new Path(getAVRIncludePath());
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.devicedescription.IDeviceDescriptionProvider#getErrorMessage()
*/
public String getErrorMessage() {
return fInternalErrorMsg + " Check the preferences for a correct path setting";
}
/**
* Initialize the list of fMCUNamesMap by opening the <avr/io.h> file and parsing it for all
* defined MCUs.
*
* throws IOException if there was an error opening or reading the file.
*/
private void loadDevices() throws IOException {
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(getAVRiohFile()));
} catch (FileNotFoundException fnfe) {
fInternalErrorMsg = "Cannot find <avr/io.h> (looked here: " + getAVRiohFile() + ").";
throw fnfe;
}
fMCUNamesMap = new HashMap<String, String>();
List<String> curDev = new ArrayList<String>();
String line;
Pattern defPat = Pattern.compile("__AVR_(.*?)__");
Pattern incPat = Pattern.compile("^# include <(.*)>");
Matcher m;
try {
while ((line = in.readLine()) != null) {
m = defPat.matcher(line);
// There may be more than one "__AVR_xxxx__" entry per line
while (m.find()) {
curDev.add(m.group(1));
}
m = incPat.matcher(line);
if (m.matches() && curDev.size() != 0) {
for (String dev : curDev) {
fMCUNamesMap.put(AVRMCUidConverter.name2id(dev), m.group(1));
}
curDev.clear();
}
}
} catch (IOException ioe) {
fInternalErrorMsg = "Cannot read <avr/io.h> (" + getAVRiohFile() + ").";
throw ioe;
}
fInternalErrorMsg = null;
in.close();
}
private void readDeviceHeader(DeviceDescription device, String headerfile) throws IOException {
String line;
device.addHeaderFile(headerfile);
BufferedReader in;
File hfile = new File(getAVRIncludePath() + "/" + headerfile);
try {
// Open a reader on the given header file and fail
// if it can't be opened
in = new BufferedReader(new FileReader(hfile));
} catch (FileNotFoundException fnfe) {
fInternalErrorMsg = "Cannot open source header file \"" + hfile.getAbsolutePath()
+ "\".";
throw fnfe;
}
Pattern incPat = Pattern.compile("^#include <(.+)>.*");
Pattern descPat = Pattern.compile("/\\* (?:RegDef\\: )?(.+) \\*/.*");
Pattern ivecPatNew = Pattern.compile("^#define ([A-Z0-9_]+_vect)\\s+_VECTOR\\((\\d+)\\).*");
Pattern ivecPatOld = Pattern.compile("^#define (SIG_[A-Z0-9_]+)\\s+_VECTOR\\((\\d+)\\).*");
Pattern portPat = Pattern
.compile("^#define ((?:PORT|PIN|DDR)[A-Z])\\s+_SFR_IO(\\d+)\\s*\\((0[xX].*)\\).*");
Pattern regPat = Pattern
.compile("^#define ([A-Z0-9]+)\\s+_SFR_(IO|MEM)(\\d+)\\s*\\((0[xX].*)\\).*");
Matcher m;
List<ICategory> categories = device.getCategories();
ICategory regCategory = categories.get(0);
ICategory portCategory = categories.get(1);
ICategory ivecCategory = categories.get(2);
// Stores the last comment in the source code
String activeDesc = "";
try {
while ((line = in.readLine()) != null) {
// Test if current line contains an #include directive
m = incPat.matcher(line);
if (m.matches()) {
// Yes: Recurse into this file if its not <avr/sfr_defs.h>
// or
// outside the <avr/*> directory
// (<avr/sfr_defs.h> because this file has some comments
// that
// are picked up as
// register definitions)
String incfilename = m.group(1);
if (!("avr/sfr_defs.h").equals(incfilename)) {
if (incfilename.startsWith("avr")) {
readDeviceHeader(device, m.group(1));
}
}
continue;
}
// Test if current line contains a descriptive comment
m = descPat.matcher(line);
if (m.matches()) {
// Yes: remember it and add it as a description to all
// following
// items
activeDesc = m.group(1);
continue;
}
if (line.trim().equals("")) {
// but don't carry activeDesc over empty lines
activeDesc = "";
continue;
}
// Test if current line defines a Interrupt vector (new style)
m = ivecPatNew.matcher(line);
if (m.matches()) {
// Yes: Add it to the interrupt vectors list
InterruptVector ivec = null;
String name = m.group(1);
String vector = m.group(2);
// test if an ivec with the old style name has already been
// created
List<IEntry> children = ivecCategory.getChildren();
if (children != null) {
for (IEntry child : children) {
if (child.getColumnData(IVecsCategory.IDX_VECTOR).equals(vector)) {
ivec = (InterruptVector) child;
break;
}
}
}
if (ivec == null) {
ivec = new InterruptVector(ivecCategory);
}
ivec.setName(name);
ivec.setDescription(activeDesc);
ivec.setVector(vector);
continue;
}
// Test if current line defines a Interrupt vector (old style)
m = ivecPatOld.matcher(line);
if (m.matches()) {
// Yes: Add it to the interrupt vectors list (but only as
// SIGname)
InterruptVector ivec = null;
String signame = m.group(1);
String vector = m.group(2);
// test if an ivec with the new style name has already been
// created (same vector number)
List<IEntry> children = ivecCategory.getChildren();
if (children != null) {
for (IEntry child : children) {
if (child.getColumnData(IVecsCategory.IDX_VECTOR).equals(vector)) {
ivec = (InterruptVector) child;
break;
}
}
}
if (ivec == null) {
ivec = new InterruptVector(ivecCategory);
}
ivec.setSIGName(signame);
ivec.setDescription(activeDesc);
ivec.setVector(vector);
continue;
}
// Test if current line defines a Port Register
m = portPat.matcher(line);
if (m.matches()) {
// Yes: Add it to the Ports list
Port port = new Port(portCategory);
port.setName(m.group(1));
port.setDescription(activeDesc);
port.setBits(m.group(2));
port.setAddr(m.group(3));
continue;
}
// Test if current line defines a Register
m = regPat.matcher(line);
if (m.matches()) {
// Yes: Add it to the Register list
Register register = new Register(regCategory);
register.setName(m.group(1));
register.setDescription(activeDesc);
register.setAddrType(m.group(2));
register.setBits(m.group(3));
register.setAddr(m.group(4));
}
}
} catch (IOException ioe) {
fInternalErrorMsg = "Cannot read source header file \"" + hfile.getAbsolutePath()
+ "\".";
throw ioe;
}
in.close();
fInternalErrorMsg = null;
}
/**
* Retrieves the path to the avr/io.h file from the plugin preferences.
*
* @return String containing the path
*/
protected String getAVRiohFile() {
IPath avriohpath = fPathProvider.getPath().append("avr").append("io.h");
return avriohpath.toOSString();
}
/**
* Retrieves the path to the include/avr directory from the Plugin preferences.
*
* @return String containing the path
*/
private String getAVRIncludePath() {
IPath includepath = fPathProvider.getPath();
return includepath.toOSString();
}
public void propertyChange(PropertyChangeEvent event) {
// check if the path to the <avr/io.h> file has changed
if (event.getProperty().equals(AVRPath.AVRINCLUDE.name())) {
// Yes: reset the devicelist and fire an event to all of our
// own listeners
fMCUNamesMap = null;
fireProviderChangeEvent();
}
}
private void fireProviderChangeEvent() {
for (IProviderChangeListener pcl : fChangeListeners) {
if (pcl != null) {
pcl.providerChange();
}
}
}
public void addProviderChangeListener(IProviderChangeListener pcl) {
if (pcl != null && !(fChangeListeners.contains(pcl))) {
fChangeListeners.add(pcl);
}
}
public void removeProviderChangeListener(IProviderChangeListener pcl) {
if (fChangeListeners.contains(pcl)) {
fChangeListeners.remove(pcl);
}
}
}