/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue;
import java.awt.Color;
import java.awt.LayoutManager;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.xml.sax.InputSource;
import tufts.Util;
import tufts.vue.gui.GUI;
public abstract class ContentViewer extends JPanel {
public static final long serialVersionUID = 1;
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(ContentViewer.class);
protected static int vCount = 0;
protected static final JLabel StatusLabel = new JLabel(VueResources.getString("addLibrary.loading.label"), JLabel.CENTER);
protected static final JComponent Status;
protected BrowseDataSource browserDS = null;
public ContentViewer() {
super();
}
public ContentViewer(LayoutManager layout) {
super(layout);
}
public ContentViewer(boolean isDoubleBuffered) {
super(isDoubleBuffered);
}
public ContentViewer(LayoutManager layout, boolean isDoubleBuffered) {
super(layout, isDoubleBuffered);
}
public void finalize() {
browserDS = null;
}
static {
GUI.apply(GUI.StatusFace, StatusLabel);
StatusLabel.setAlignmentX(0.5f);
JProgressBar bar = new JProgressBar();
bar.setIndeterminate(true);
if (false && Util.isMacLeopard()) {
bar.putClientProperty("JProgressBar.style", "circular");
bar.setBorder(BorderFactory.createLineBorder(Color.darkGray));
//bar.putClientProperty("JComponent.sizeVariant", "small"); // no effect
//bar.setString("Loading...");// no effect on mac
//bar.setStringPainted(true); // no effect on mac
} else {
if (DEBUG.BOXES) bar.setBorder(BorderFactory.createLineBorder(Color.green));
bar.setBackground(Color.red);
bar.setEnabled(false); // don't make so garish (mostly for mac)
}
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
if (DEBUG.BOXES) StatusLabel.setBorder(BorderFactory.createLineBorder(Color.blue, 1));
panel.add(StatusLabel);
panel.add(Box.createVerticalStrut(5));
panel.add(bar);
panel.setBorder(GUI.WidgetInsetBorder3);
Status = panel;
}
protected abstract void displayInBrowsePane(JComponent viewer, boolean priority);
protected abstract void repaintList();
// Overridden in DataSourceViewer -- not needed in DataSetViewer
public void setActiveDataSource(edu.tufts.vue.dsm.DataSource ds) {}
// Overridden in DataSetViewer -- not needed in DataSourceViewer
public void setActiveDataSource(final tufts.vue.DataSource ds) {}
tufts.vue.DataSource getBrowsedDataSource() {
return browserDS;
}
protected JComponent produceViewer(final tufts.vue.BrowseDataSource ds) {
return produceViewer(ds, false);
}
protected JComponent produceViewer(final tufts.vue.BrowseDataSource ds, final boolean caching) {
/**
There is a weird problem in the latest Java 1.6 code on
all the Sun JVMs running in the browser where this test fails even though it should not.
*/
if (!SwingUtilities.isEventDispatchThread())
{
if (VUE.isApplet())
System.out.println("not threadsafe except for AWT");
// else
throw new Error("not threadsafe except for AWT");
}
if (DEBUG.DR) Log.debug("produceViewer: " + ds);
final JComponent viewer = ds.getResourceViewer();
if (viewer != null)
return viewer;
StatusLabel.setText(statusName(ds));
if (ds.isLoading()) {
// could up priority any time we come back through
//ds.getLoadThread().setPriority(Thread.MAX_PRIORITY);
//ds.getLoadThread().setPriority(Thread.NORM_PRIORITY);
return Status;
}
String s = ds.getClass().getSimpleName() + "[" + ds.getDisplayName();
if (ds.getAddressName() != null)
s += "; " + ds.getAddressName();
final String name = s + "]";
final Thread buildViewerThread =
new Thread(String.format("VBLD-%02d %s", vCount++, name)) {
{
setDaemon(true);
if (caching)
setPriority(Thread.currentThread().getPriority() - 1);
}
@Override
public void run() {
if (DEBUG.DR)Log.debug("kicked off");
final JComponent newViewer = buildViewer(ds);
if (isInterrupted()) {
if (DEBUG.DR && newViewer != null)
Log.debug("produced; but not needed: aborting");
return;
}
Log.info("produced " + GUI.name(newViewer));
GUI.invokeAfterAWT(new AWTAcceptViewerTask(ds, this, newViewer, name));
}
};
ds.setLoadThread(buildViewerThread);
buildViewerThread.start();
return Status;
}
/**
*
* @return either the successfully created viewer, or an as informative as possible
* error report panel should we encounter any exceptions. The idea is that this
* method is guaranteed not to return null: always something meaninful to display.
* With one exception: if the thread this is running on has it's interrupted status
* set, it may return null.
*
*/
protected JComponent buildViewer(final tufts.vue.BrowseDataSource ds)
{
final String address = ds.getAddress();
JComponent viewer = null;
Throwable exception = null;
try {
viewer = ds.buildResourceViewer();
} catch (Throwable t) {
exception = t;
}
if (Thread.currentThread().isInterrupted()) {
if (DEBUG.DR) Log.debug("built; but not needed: aborting");
return null;
}
if (exception == null && viewer == null)
exception = new Exception("no viewer available");
if (exception != null) {
final Throwable t = exception;
Log.error(ds + "; getResourceViewer:", t);
String txt = ds.getTypeName() + " unavailable:";
if (t instanceof DataSourceException) {
if (t.getMessage() != null)
txt += " " + t.getMessage();
} else
txt += "\n\nError: " + prettyException(t);
if (t.getCause() != null) {
Throwable c = t.getCause();
Log.error("FULL CAUSE:", c);
txt += "\n\nCause: " + prettyException(c);
}
String a = address;
if (a != null) {
//if (a.length() == 0 || Character.isWhitespace(a.charAt(0)) || Character.isWhitespace(a.charAt(a.length()-1)))
a = '[' + a + ']';
}
txt += "\n\nConfiguration address: " + a;
if (DEBUG.Enabled)
txt += "\n\nDataSource: " + ds.getClass().getName();
txt += "\n\nThis could be a problem with the configuration for this "
+ ds.getTypeName()
+ ", with the local network connection, or with a remote server.";
if (DEBUG.Enabled)
txt += "\n\n" + Thread.currentThread();
viewer = new ErrorText(txt);
}
return viewer;
}
public static void marshallMap(File file,SaveDataSourceViewer dataSourceViewer) {
Marshaller marshaller = null;
try {
FileWriter writer = new FileWriter(file);
marshaller = new Marshaller(writer);
marshaller.setMapping(tufts.vue.action.ActionUtil.getDefaultMapping());
if (DEBUG.DR) Log.debug("marshallMap: marshalling " + dataSourceViewer + " to " + file + "...");
marshaller.setNoNamespaceSchemaLocation("none");
marshaller.marshal(dataSourceViewer);
if (DEBUG.DR) Log.debug("marshallMap: done marshalling.");
writer.flush();
writer.close();
} catch (Throwable t) {
t.printStackTrace();
System.err.println("DataSourceViewer.marshallMap " + t.getMessage());
}
}
public SaveDataSourceViewer unMarshallMap(File file)
throws java.io.IOException,
org.exolab.castor.xml.MarshalException,
org.exolab.castor.xml.ValidationException,
org.exolab.castor.mapping.MappingException
{
Unmarshaller unmarshaller = tufts.vue.action.ActionUtil.getDefaultUnmarshaller(file.toString());
FileReader reader = new FileReader(file);
SaveDataSourceViewer sviewer = (SaveDataSourceViewer) unmarshaller.unmarshal(new InputSource(reader));
reader.close();
return sviewer;
}
protected static String statusName(tufts.vue.BrowseDataSource ds) {
String s = ds.getAddressName();
if (s == null)
s = ds.getDisplayName();
if (s == null)
s = ds.getTypeName();
return s;
}
protected String prettyException(Throwable t) {
String txt;
if (t.getClass().getName().startsWith("java"))
txt = t.getClass().getSimpleName();
else
txt = t.getClass().getName();
if (t.getMessage() != null)
txt += ": " + t.getMessage();
return txt;
}
protected static final class ErrorText extends JTextArea {
public static final long serialVersionUID = 1;
ErrorText(String txt) {
super(txt);
setEditable(false);
setLineWrap(true);
setWrapStyleWord(true);
setBorder(GUI.WidgetInsetBorder3);
GUI.apply(GUI.StatusFace, this);
//setOpaque(false);
//GUI.apply(GUI.ErrorFace, this);
}
}
protected class AWTAcceptViewerTask implements Runnable {
final tufts.vue.BrowseDataSource ds;
final Thread serviceThread;
final JComponent newViewer;
final String name;
AWTAcceptViewerTask(BrowseDataSource ds, Thread serviceThread, JComponent viewer, String name) {
this.ds = ds;
this.serviceThread = serviceThread;
this.newViewer = viewer;
this.name = name;
}
public void run() {
if (serviceThread.isInterrupted()) {
// never possible? we're now synchronous in AWT
Log.warn(name + "; in AWT; but viewer no longer needed: aborting result for " + serviceThread, new Throwable("FYI"));
return;
}
VUE.diagPush(name);
if (DEBUG.Enabled) Log.debug("accepting viewer & setting into VueDataSource");
ds.setViewer(newViewer); // important to do this in AWT; it's why we have this task
// The viewer we've just set may actually be just a text pane
// describing an error condition: now set the actual availablity
// of the content:
ds.setAvailable(newViewer instanceof ErrorText == false);
repaintList(); // so change in loaded status will be visible
if (browserDS == ds) { // important to check this in AWT;
if (DEBUG.Enabled) Log.debug("currently displayed data-source wants this viewer; displaying");
displayInBrowsePane(newViewer, false); // important to do this in AWT;
}
else
if (DEBUG.DR) Log.debug("display: skipping; user looking at something else");
// this would always fallback-interrupt our own serviceThread but by now it
// has already exited is waiting to die, as the last thing it does is add
// this task to the AWT event queue. There should be no code in the run
// after the invoke. We check for isAlive in setLoadThread just in case,
// before we fallback-interrupt.
// important to do both the get/set in AWT:
if (ds.getLoadThread() == serviceThread)
ds.setLoadThread(null);
VUE.diagPop();
}
}
}