Adding initial files

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@944083 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2010-05-14 02:14:30 +00:00
parent b0993e01cd
commit 3887eb414e
59 changed files with 7238 additions and 0 deletions

View File

@ -0,0 +1,61 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import javax.persistence.metamodel.Attribute;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* Displays color codes of each attribute type.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class AttributeLegendView extends JPanel {
public AttributeLegendView() {
super(true);
setBorder(BorderFactory.createTitledBorder("Attribute Legends"));
setLayout(new GridLayout(0,3));
add(createColoredLabel("IDENTITY", Color.RED));
add(createColoredLabel("VERSION", Color.DARK_GRAY));
for (Attribute.PersistentAttributeType type : Attribute.PersistentAttributeType.values()) {
add(createColoredLabel(type.toString().replace('_', ' '), MetamodelHelper.getColor(type)));
}
}
JComponent createColoredLabel(String text, Color c) {
int width = 40;
int height = 20;
BufferedImage bimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bimage.setRGB(i, j, c.getRGB());
JLabel label = new JLabel(text, new ImageIcon(bimage), JLabel.LEADING);
return label;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import javax.persistence.metamodel.Attribute;
import javax.swing.JLabel;
/**
* View of a persistent attribute as a JLabel.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class AttributeView extends JLabel {
private final Attribute<?,?> _attr;
public AttributeView(Attribute<?,?> attr) {
_attr = attr;
Color color = MetamodelHelper.getColor(attr);
setForeground(color);
setText(MetamodelHelper.getDisplayName(attr));
}
public Attribute<?,?> getAttribute() {
return _attr;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.BorderLayout;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
/**
* Displays Properties.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class ConfigurationViewer extends JTextPane {
private static final char SPACE = ' ';
public ConfigurationViewer(String title, Map<String, Object> config) {
super();
setBorder(BorderFactory.createTitledBorder(title));
TreeSet<String> sortedKeys = new TreeSet<String>(config.keySet());
int L = getMaxLength(sortedKeys);
for (String key : sortedKeys) {
setCaretPosition(getDocument().getLength());
setCharacterAttributes(TextStyles.KEYS, true);
replaceSelection(key + pad(L - key.length()) + ":");
setCaretPosition(getDocument().getLength());
setCharacterAttributes(TextStyles.VALUES, true);
replaceSelection(toString(config.get(key)) + "\r\n");
}
}
String toString(Object value) {
if (value == null)
return "null";
if (value.getClass().isArray()) {
return Arrays.toString((Object[])value);
}
return value.toString();
}
private int getMaxLength(Set<String> keys) {
int len = 1;
for (String s : keys) {
len = Math.max(s.length(), len);
}
return len;
}
private String pad(int n) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < n; i++)
buf.append(SPACE);
return buf.toString();
}
}

View File

@ -0,0 +1,260 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
/**
* A data model for a tabular view of a list of persistent entities.
* The data supplied by this model can be filtered to display field values of
* basic type or single-valued or multi-valued relationships.
* <br>
* The meta-information about the attributes of the entity are supplied by
* newly defined {@link Metamodel meta-model API} of JPA 2.0 specification.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class EntityDataModel<T> extends AbstractTableModel implements TableModel, Iterable<T> {
/**
* Constant designates to include non-relation fields.
*/
public static int BASIC_ATTR = 1 << 0;
/**
* Constant designates to include single-valued relation fields.
*/
public static int ASSOCIATION_ATTR = 1 << 1;
/**
* Constant designates to include multi-valued relation fields.
*/
public static int PLURAL_ATTR = 1 << 2;
/**
* Constant designates to include all fields.
*/
public static int ALL_ATTR = BASIC_ATTR | ASSOCIATION_ATTR | PLURAL_ATTR;
/**
* Constant designates to show a row count field at the first column.
*/
public static int ROW_COUNT = 1 << 3;
private List<String> columnNames;
private List<Class<?>> columnClasses;
private List<Method> methods;
private List<T> data;
private List<Attribute<? super T,?>> attributes;
private static Object[] EMPTY_ARGS = null;
private static Class<?>[] EMPTY_CLASSES = null;
private boolean showsRowCount;
private boolean showsBasicAttr;
private boolean showsSingularAttr;
private boolean showsPluralAttr;
/**
* Attributes of the entity are reordered with basic attributes, followed by singular
* association followed by the many-valued attributes.
*
* @param cls
* @param data
* @param meta
*/
public EntityDataModel(Class<T> cls, List<T> data, Metamodel meta, int styleBits) {
super();
this.data = data;
EntityType<T> entityType = meta.entity(cls);
columnNames = new ArrayList<String>();
columnClasses = new ArrayList<Class<?>>();
attributes = new ArrayList<Attribute<? super T,?>>();
methods = new ArrayList<Method>();
showsRowCount = (styleBits & ROW_COUNT) != 0;
showsBasicAttr = (styleBits & BASIC_ATTR) != 0;
showsSingularAttr = (styleBits & ASSOCIATION_ATTR) != 0;
showsPluralAttr = (styleBits & PLURAL_ATTR) != 0;
Set<SingularAttribute<? super T, ?>> sAttrs = entityType.getSingularAttributes();
if (showsBasicAttr) {
for (SingularAttribute<? super T, ?> attr : sAttrs) {
if (!attr.isAssociation()) {
attributes.add(attr);
}
}
}
if (showsSingularAttr) {
for (SingularAttribute<? super T, ?> attr : sAttrs) {
if (attr.isAssociation()) {
attributes.add(attr);
}
}
}
if (showsPluralAttr) {
Set<PluralAttribute<? super T, ?, ?>> pAttrs = entityType.getPluralAttributes();
for (PluralAttribute<? super T, ?, ?> attr : pAttrs) {
attributes.add(attr);
}
}
Collections.sort(attributes, new MetamodelHelper.AttributeComparator());
for (Attribute<? super T, ?> attr : attributes) {
populate(cls, attr);
}
if (showsRowCount) {
columnNames.add(0,"Row");
columnClasses.add(0, Long.class);
attributes.add(0, null);
methods.add(0, null);
}
}
private void populate(Class<T> cls, Attribute<?,?> attr) {
columnNames.add(attr.getName());
columnClasses.add(wrap(attr.getJavaType()));
methods.add(getMethod(cls, attr.getName()));
}
/**
* Gets the attribute at a given column index.
* Can be null for 0-th index if row count is being shown.
*/
public Attribute<?,?> getAttribute(int columnIndex) {
return attributes.get(columnIndex);
}
/**
* Gets the entity represented in the given row.
*/
public T getRow(int row) {
return data.get(row);
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnClasses.get(columnIndex);
}
@Override
public int getColumnCount() {
return columnNames.size();
}
@Override
public String getColumnName(int columnIndex) {
return columnNames.get(columnIndex);
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Method method = methods.get(columnIndex);
if (method == null) {
return columnIndex == 0 && showsRowCount ? rowIndex+1 : "?";
}
Object row = data.get(rowIndex);
Object val = getValueByReflection(rowIndex, row, columnIndex, method);
return val;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
// should never be called
}
/**
* Gets the value by reflection.
* @param o
* @param p
* @return
*/
Object getValueByReflection(int rowIndex, Object o, int columnIndex, Method m) {
if (o == null) {
System.err.println("Row " + rowIndex + " is null");
return null;
}
if (m == null) {
System.err.println("Column " + columnIndex + ":" + getColumnName(columnIndex) + " method is null");
return null;
}
try {
return m.invoke(o, EMPTY_ARGS);
} catch (Exception e) {
System.err.println("Can not extract value at [" + rowIndex + "," + columnIndex + "] due to " + e);
e.printStackTrace();
}
return null;
}
Class<?> wrap(Class<?> c) {
if (c == null || c.isInterface() || c.isArray())
return Object.class;
if (c.isPrimitive()) {
if (c == boolean.class) return Boolean.class;
if (c == byte.class) return Byte.class;
if (c == char.class) return Character.class;
if (c == double.class) return Double.class;
if (c == float.class) return Float.class;
if (c == int.class) return Integer.class;
if (c == long.class) return Long.class;
if (c == short.class) return Short.class;
}
return c;
}
private Method getMethod(Class<T> type, String p) {
try {
String getter = "get" + Character.toUpperCase(p.charAt(0))+p.substring(1);
return type.getMethod(getter, EMPTY_CLASSES);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public void updateData(List<T> newData) {
data = newData;
fireTableDataChanged();
}
@Override
public Iterator<T> iterator() {
return data.iterator();
}
}

View File

@ -0,0 +1,207 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.util.List;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.metamodel.Attribute;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.StateManager;
/**
* A specialized Swing table to display a list of persistent entity instances.
* The data for the table is supplied via a specialized {@linkplain EntityDataModel data model}
* that is supplied at construction.
* <br>
* The table view uses specialized cell renderer to display single-valued and multi-valued
* association.
*
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class EntityTable<T> extends JTable {
private InstanceCellRenderer instanceCellRenderer;
private CollectionCellRenderer collectionCellRenderer;
public EntityTable(Class<T> cls, List<T> data, int styleBits, EntityManagerFactory unit) {
super();
instanceCellRenderer = new InstanceCellRenderer(unit.getPersistenceUnitUtil());
collectionCellRenderer = new CollectionCellRenderer();
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setModel(new EntityDataModel<T>(cls, data, unit.getMetamodel(), styleBits));
getModel().addTableModelListener(this);
initColumnSizes();
setFillsViewportHeight(true);
}
public InstanceCellRenderer getInstanceRenderer() {
return instanceCellRenderer;
}
/**
* Gets the special renderer for single- and multi-valued association.
* Otherwise uses the super classes' renderer defined by the field type.
*/
public TableCellRenderer getCellRenderer(int row, int column) {
Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
TableCellRenderer renderer = null;
if (attr == null) {
renderer = super.getCellRenderer(row, column);
} else if (attr.isAssociation()) {
renderer = instanceCellRenderer;
} else if (attr.isCollection()) {
renderer = collectionCellRenderer;
} else {
renderer = super.getCellRenderer(row, column);
}
if (renderer instanceof DefaultTableCellRenderer) {
((DefaultTableCellRenderer)renderer).setHorizontalAlignment(JLabel.CENTER);
}
return renderer;
}
public TableCellEditor getCellEditor(int row, int column) {
Attribute<?,?> attr = ((EntityDataModel)getModel()).getAttribute(column);
if (attr == null)
return super.getCellEditor(row, column);
if (attr.isCollection())
return new DefaultCellEditor((JComboBox)getCellRenderer(row, column));
return super.getCellEditor(row, column);
}
/**
* Picks good column sizes.
* If all column heads are wider than the column's cells'
* contents, then you can just use column.sizeWidthToFit().
*/
private void initColumnSizes() {
TableModel model = getModel();
TableColumn column = null;
Component comp = null;
int headerWidth = 0;
TableCellRenderer headerRenderer = getTableHeader().getDefaultRenderer();
for (int columnIndex = 0; columnIndex < model.getColumnCount(); columnIndex++) {
int cellWidth = 0;
column = getColumnModel().getColumn(columnIndex);
comp = headerRenderer.getTableCellRendererComponent(
null, column.getHeaderValue(),
false, false, 0, 0);
headerWidth = comp.getPreferredSize().width;
TableCellRenderer renderer = getCellRenderer(0, columnIndex);
int rowCount = Math.min(model.getRowCount(), 10);
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Object value = model.getValueAt(rowIndex, columnIndex);
comp = renderer.getTableCellRendererComponent(
this, value,
false, false, rowIndex, columnIndex);
cellWidth = Math.max(comp.getPreferredSize().width, cellWidth);
}
column.setPreferredWidth(Math.max(headerWidth, cellWidth));
}
}
/**
* Renders the value of a persistent entity in a table column as the persistent identifier.
* The persistent identifier is extracted by the new {@link PersistenceUnitUtil utility} feature
* of JPA 2.0 API.
*
* @author Pinaki Poddar
*
*/
public class InstanceCellRenderer extends DefaultTableCellRenderer {
private final PersistenceUnitUtil util;
public InstanceCellRenderer(PersistenceUnitUtil util) {
super();
this.util = util;
}
/**
* Gets the stringified persistence identifier of the given instance.
*
*/
public String renderAsString(Object instance) {
if (instance == null) {
return "null";
}
Object id = util.getIdentifier(instance);
if (id != null)
return id.toString();
if (instance instanceof PersistenceCapable) {
PersistenceCapable pc = (PersistenceCapable)instance;
StateManager sm = pc.pcGetStateManager();
id = sm == null ? null : sm.fetchObjectId();
}
return id == null ? "null" : id.toString();
}
public void setValue(Object instance) {
setForeground(Color.BLUE);
setText(renderAsString(instance));
}
}
/**
* Renders a many-valued attribute as simply three dots.
*
* @author Pinaki Poddar
*
*/
public class CollectionCellRenderer extends DefaultTableCellRenderer {
public CollectionCellRenderer() {
setPreferredSize(new Dimension(10,20));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
return super.getTableCellRendererComponent(table, value == null ? null : "...",
isSelected, hasFocus, row, column);
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.persistence.EntityManagerFactory;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import jpa.tools.swing.EntityTable.InstanceCellRenderer;
/**
* An entity table view consists of a JTable and optionally another table for many-valued associations.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class EntityTableView<T> extends JPanel implements ListSelectionListener {
private EntityTable<T> _table;
private JTextArea _details;
public EntityTableView(Class<T> cls, int styleBits, EntityManagerFactory unit) {
this(cls, (List<T>)Collections.EMPTY_LIST, styleBits, unit);
}
public EntityTableView(Class<T> cls, List<T> data, int styleBits, EntityManagerFactory unit) {
super(true);
_table = new EntityTable<T>(cls, data, styleBits, unit);
_table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
if ((styleBits & EntityDataModel.PLURAL_ATTR) != 0) {
_table.getSelectionModel().addListSelectionListener(this);
_details = new JTextArea("Click many-valued columns for display");
_details.setForeground(Color.LIGHT_GRAY);
_details.setBorder(BorderFactory.createTitledBorder("Many-valued association"));
_details.setEditable(false);
}
setBorder(BorderFactory.createTitledBorder(_table.getModel().getRowCount() + " " +
unit.getMetamodel().entity(cls).getName()));
setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
add(new JScrollPane(_table,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
if (_details != null)
add(_details);
}
public EntityTable<T> getTable() {
return _table;
}
public EntityDataModel<T> getDataModel() {
return (EntityDataModel<T>)_table.getModel();
}
public void updateTitle(String txt) {
Border border = getBorder();
if (border instanceof TitledBorder) {
((TitledBorder)border).setTitle(txt);
repaint();
}
}
/**
* Notified when a cell in the table is selected.
* If the selected cell is a many-valued attribute then displays its details in the details pane.
*/
@Override
public void valueChanged(ListSelectionEvent e) {
int row = _table.getSelectedRow();
if (row == -1)
return;
int col = _table.getSelectedColumn();
if (col == -1)
return;
EntityDataModel<?> model = (EntityDataModel<?>)_table.getModel();
Attribute<?,?> attr = model.getAttribute(col);
if (attr.isCollection()) {
Object val = model.getValueAt(row, col);
showDetails(attr, val);
}
}
private void showDetails(Attribute<?,?> attr, Object val) {
_details.setText(null);
ManagedType<?> owner = attr.getDeclaringType();
String title = (owner instanceof EntityType)
? ((EntityType<?>)owner).getName() + "." + attr.getName()
: owner.getJavaType().getSimpleName() + "." + attr.getName();
TitledBorder border = (TitledBorder)_details.getBorder();
if (val == null) {
border.setTitle(title + " (null)");
} else {
Collection<?> coll = (Collection<?>)val;
if (attr.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) {
border.setTitle(title + " (" + coll.size() + " values)");
_details.setForeground(Color.BLACK);
for (Object e : coll) {
_details.append(_table.getInstanceRenderer().renderAsString(e) + "\r\n");
_details.append(e + "\r\n");
}
} else {
border.setTitle(title + " (" + coll.size() + " instances)");
_details.setForeground(Color.BLUE);
InstanceCellRenderer renderer = _table.getInstanceRenderer();
for (Object e : coll) {
_details.append(renderer.renderAsString(e) + "\r\n");
}
}
}
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.util.List;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;
/**
* View of a persistent entity type as a JPanel.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class EntityTypeView<T> extends JPanel {
final EntityType<T> type;
final int hgap = 4;
final int vgap = 4;
public EntityTypeView(EntityType<T> type) {
this.type = type;
GridLayout layout = new GridLayout(0,1);
setLayout(layout);
layout.setVgap(vgap);
layout.setHgap(hgap);
setBackground(Color.WHITE);
setBorder(BorderFactory.createTitledBorder(MetamodelHelper.getDisplayName(type)));
int w = 0;
int h = 0;
List<Attribute<? super T,?>> attrs = MetamodelHelper.getAttributes(type);
for (Attribute<? super T,?> attr : attrs) {
JComponent c = new AttributeView(attr);
add(c);
w = Math.max(w, c.getPreferredSize().width);
h += c.getPreferredSize().height + 2*vgap;
}
// setPreferredSize(new Dimension(w,h));
}
public EntityType<T> getEntityType() {
return type;
}
/**
* Gets the top left position of the attribute label relative to this entity view.
* @param attr
* @return
*/
public Point getPosition(Attribute<?,?> attr) {
for (Component c : getComponents()) {
if (c instanceof AttributeView) {
if (((AttributeView)c).getAttribute().equals(attr)) {
return new Point(c.getLocation().x, c.getLocation().y + c.getHeight()/2);
}
}
}
return new Point(0,0);
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
@SuppressWarnings("serial")
public class ErrorDialog extends JDialog {
private static List<String> filters = Arrays.asList("java.awt.", "javax.swing.", "sun.reflect.");
private static AttributeSet red, black;
static {
StyleContext ctx = StyleContext.getDefaultStyleContext();
red = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED);
red = ctx.addAttribute(red, StyleConstants.Bold, true);
red = ctx.addAttribute(red, StyleConstants.FontSize, 12);
red = ctx.addAttribute(red, StyleConstants.FontFamily, "Courier");
black = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.BLACK);
black = ctx.addAttribute(black, StyleConstants.Bold, false);
black = ctx.addAttribute(black, StyleConstants.FontFamily, "Courier");
}
public ErrorDialog(JComponent owner, Throwable t) {
super();
setModal(true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
// Icon icon = Images.ERROR;
// setIconImage(((ImageIcon)icon).getImage());
addException(t);
pack();
SwingHelper.position(this, owner);
}
public ErrorDialog(Throwable t) {
this(null, t);
}
void addException(Throwable t) {
setTitle("Error");
String txt = t.getClass().getName() + ":" + t.getLocalizedMessage();
JTextArea message = new JTextArea(txt);
message.setLineWrap(true);
message.setWrapStyleWord(true);
message.setForeground(Color.RED);
message.setText(txt);
message.setEditable(false);
JTextPane window = new JTextPane();
printStackTrace(t, window);
window.setEditable(false);
JScrollPane pane = new JScrollPane(window,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
pane.setPreferredSize(new Dimension(400, 200));
pane.setBorder(BorderFactory.createTitledBorder("Stacktrace"));
pane.setPreferredSize(new Dimension(400, 200));
JPanel main = new JPanel();
main.setLayout(new BorderLayout());
main.add(message, BorderLayout.NORTH);
main.add(pane, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
JButton ok = new JButton("OK");
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ErrorDialog.this.dispose();
}
});
buttonPanel.add(ok);
main.add(buttonPanel, BorderLayout.SOUTH);
getContentPane().add(main);
}
static String NEWLINE = "\r\n";
StringBuilder printStackTrace(Throwable t, JTextPane text) {
String message = t.getClass().getName() + ": " + t.getMessage() + NEWLINE;
text.setCaretPosition(text.getDocument().getLength());
text.setCharacterAttributes(red, false);
text.replaceSelection(message);
StackTraceElement[] traces = t.getStackTrace();
text.setCharacterAttributes(black, false);
for (StackTraceElement e : traces) {
if (!isFiltered(e.getClassName())) {
String str = " " + e.toString() + NEWLINE;
text.setCaretPosition(text.getDocument().getLength());
text.replaceSelection(str);
}
}
Throwable cause = t.getCause();
if (cause !=null && cause != t) {
printStackTrace(cause, text);
}
return null;
}
StringBuilder toString(StackTraceElement[] traces) {
StringBuilder error = new StringBuilder();
for (StackTraceElement e : traces) {
if (!isFiltered(e.getClassName())) {
String str = e.toString();
error.append(str).append("\r\n");
}
}
return error;
}
private boolean isFiltered(String className) {
for (String s : filters) {
if (className.startsWith(s))
return true;
}
return false;
}
/**
* @param args
*/
public static void main(String[] args) {
new ErrorDialog(new IllegalArgumentException(
"This is test error with very long line of error message that should not be in a single line"))
.setVisible(true);
}
}

View File

@ -0,0 +1,41 @@
package jpa.tools.swing;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FileScanner {
private String ext;
boolean recursive;
public FileScanner(String ext, boolean recurse) {
this.ext = ext;
this.recursive = recurse;
}
/**
* Scans the given
* @param root
* @return
*/
public List<File> scan(File dir) {
List<File> bag = new ArrayList<File>();
scan(dir, bag);
return bag;
}
private void scan(File dir, List<File> bag) {
if (dir == null || !dir.exists() || !dir.isDirectory())
return;
File[] all = dir.listFiles();
for (File f : all) {
if (ext == null || f.getName().endsWith(ext)) {
bag.add(f);
}
if (recursive && f.isDirectory()) {
scan(f, bag);
}
}
}
}

View File

@ -0,0 +1,111 @@
package jpa.tools.swing;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Searches for a given class in the set of directories recursively.
* If a given directory contains jar file then searches inside the jar.
*
* Usage
* $ java find.Finder class dir1 dir2...
* where
* class is fully qualified class name
* dir name of a file system directory
*
* Example
* $ java find.Finder org.eclipse.ui.plugin.AbstractUIPlugin c:\eclipse\plugins
* will print
* org.eclipse.ui.plugin.AbstractUIPlugin found in c:\eclipse\plugins\org.eclipse.ui.workbench_3.4.1.M20080827-0800a.jar
*
* @author Pinaki Poddar
*
*/
public class Finder {
private static final String CLASS_SUFFIX = ".class";
private static final String JAR_SUFFIX = ".jar";
private static final char DOT = '.';
private static final boolean DEBUG = false;
private static void usage() {
System.err.println("Searches a given class in the given directories."
+ "\r\nIf a given directory contains jar then searches within the jar."
+ "\r\nIf a given directory contains other directories then searches "
+ "recursively.\r\n");
System.err.println("\r\n Usage:");
System.err.println(" $ java find.Finder class dir [dir...]");
System.err.println(" where");
System.err.println(" class fully-qualified class name");
System.err.println(" dir name of a directory");
System.err.println("\r\n Example:");
System.err.println(" $ java find.Finder java.lang.Object c:\\java");
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
usage();
System.exit(0);
}
Finder finder = new Finder();
for (int i = 1; i < args.length; i++) {
File root = new File(args[i]);
finder.scan(root, args[0]);
}
}
private void scan(File dir, String cls) throws IOException {
File[] classes = dir.listFiles(new FileFilter() {
public boolean accept(File path) {
return path.getName().endsWith(CLASS_SUFFIX);
}
});
String clsName = cls+CLASS_SUFFIX;
for (File c : classes) {
String name = c.getName().replace(File.separatorChar, DOT);
if (name.endsWith(clsName))
System.err.println(cls + " found in " + c.getAbsolutePath());
}
File[] jars = dir.listFiles(new FileFilter() {
public boolean accept(File path) {
return path.getName().endsWith(JAR_SUFFIX);
}
});
for (File jar : jars) {
JarFile jarFile = new JarFile(jar);
scan(jarFile, cls);
}
File[] dirs = dir.listFiles(new FileFilter() {
public boolean accept(File path) {
return path.isDirectory();
}
});
for (File cdir : dirs)
scan(cdir, cls);
}
private void scan(JarFile jar, String cls) {
String clsName = cls.replace('.', '/') + CLASS_SUFFIX;
debug("Scanning " + jar.getName() + " for [" + clsName + "]");
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();
if (entryName.endsWith(CLASS_SUFFIX))
debug(" " + entryName);
if (entryName.equals(clsName))
System.err.println(cls + " found in " + jar.getName());
}
}
private void debug(String s) {
if (DEBUG)
System.err.println(s);
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import javax.swing.text.AttributeSet;
import javax.swing.text.StyleContext;
/**
* An output stream that uses a {@link ScrollingTextPane} as its sink.
* Flushes the buffer at line end.
*
* @author Pinaki Poddar
*
*/
public class GraphicOutputStream extends OutputStream {
ScrollingTextPane _sink;
private char[] buffer = new char[1024];
private int count;
private Map<String, AttributeSet> _styles = new HashMap<String, AttributeSet>();
private static AttributeSet _defaultStyle = StyleContext.getDefaultStyleContext()
.getStyle(StyleContext.DEFAULT_STYLE);
public GraphicOutputStream(ScrollingTextPane delegate) {
_sink = delegate;
}
public void registerStyle(String pattern, AttributeSet style) {
_styles.put(pattern, style);
}
@Override
public void write(int b) throws IOException {
buffer[count++] = (char)b;
if (count > buffer.length || b == '\r' || b == '\n') {
flushBuffer();
}
}
private void flushBuffer() {
String txt = new String(buffer, 0, count);
count = 0;
AttributeSet style = getStyle(txt);
_sink.setCharacterAttributes(style, true);
_sink.appendText(txt);
}
AttributeSet getStyle(String txt) {
if (txt == null || txt.isEmpty())
return _defaultStyle;
for (String pattern : _styles.keySet()) {
if (txt.startsWith(pattern))
return _styles.get(pattern);
}
return _defaultStyle;
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import javax.swing.border.AbstractBorder;
@SuppressWarnings("serial")
public class ImageBorder extends AbstractBorder {
Image image;
String title;
// Image topCenterImage, topLeftImage, topRight;
//
// Image leftCenterImage, rightCenterImage;
//
// Image bottomCenterImage, bottomLeftImage, bottomRightImage;
Insets insets;
public ImageBorder(Image img, String title) {
image = img;
this.title = title;
}
// public ImageBorder(Image top_left, Image top_center, Image top_right, Image left_center,
// Image right_center, Image bottom_left, Image bottom_center, Image bottom_right) {
//
// this.topLeftImage = top_left;
// this.topCenterImage = top_center;
// this.topRight = top_right;
// this.leftCenterImage = left_center;
// this.rightCenterImage = right_center;
// this.bottomLeftImage = bottom_left;
// this.bottomCenterImage = bottom_center;
// this.bottomRightImage = bottom_right;
// }
public void setInsets(Insets insets) {
this.insets = insets;
}
public Insets getBorderInsets(Component c) {
if (insets != null) {
return insets;
} else {
// return new Insets(topCenterImage.getHeight(null), leftCenterImage.getWidth(null), bottomCenterImage
// .getHeight(null), rightCenterImage.getWidth(null));
return new Insets(image.getHeight(null), 100,10,100);
}
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.setColor(Color.white);
g.fillRect(x, y, width, height);
g.setColor(Color.black);
Graphics2D g2 = (Graphics2D) g;
int tlw = image.getWidth(null);
int tlh = image.getHeight(null);
fillTexture(g2, image, x, y, tlw, tlh);
g2.drawString(title, tlw, 10);
// int tlw = topLeftImage.getWidth(null);
// int tlh = topLeftImage.getHeight(null);
// int tcw = topCenterImage.getWidth(null);
// int tch = topCenterImage.getHeight(null);
// int trw = topRight.getWidth(null);
// int trh = topRight.getHeight(null);
//
// int lcw = leftCenterImage.getWidth(null);
// int lch = leftCenterImage.getHeight(null);
// int rcw = rightCenterImage.getWidth(null);
// int rch = rightCenterImage.getHeight(null);
//
// int blw = bottomLeftImage.getWidth(null);
// int blh = bottomLeftImage.getHeight(null);
// int bcw = bottomCenterImage.getWidth(null);
// int bch = bottomCenterImage.getHeight(null);
// int brw = bottomRightImage.getWidth(null);
// int brh = bottomRightImage.getHeight(null);
// fillTexture(g2, topLeftImage, x, y, tlw, tlh);
// fillTexture(g2, topCenterImage, x + tlw, y, width - tlw - trw, tch);
// fillTexture(g2, topRight, x + width - trw, y, trw, trh);
//
// fillTexture(g2, leftCenterImage, x, y + tlh, lcw, height - tlh - blh);
// fillTexture(g2, rightCenterImage, x + width - rcw, y + trh, rcw, height - trh - brh);
//
// fillTexture(g2, bottomLeftImage, x, y + height - blh, blw, blh);
// fillTexture(g2, bottomCenterImage, x + blw, y + height - bch, width - blw - brw, bch);
// fillTexture(g2, bottomRightImage, x + width - brw, y + height - brh, brw, brh);
}
public void fillTexture(Graphics2D g2, Image img, int x, int y, int w, int h) {
BufferedImage buff = createBufferedImage(img);
Rectangle anchor = new Rectangle(x, y, img.getWidth(null), img.getHeight(null));
TexturePaint paint = new TexturePaint(buff, anchor);
g2.setPaint(paint);
g2.fillRect(x, y, w, h);
}
public BufferedImage createBufferedImage(Image img) {
BufferedImage buff = new BufferedImage(img.getWidth(null), img.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics gfx = buff.createGraphics();
gfx.drawImage(img, 0, 0, null);
gfx.dispose();
return buff;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.util.List;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.swing.table.AbstractTableModel;
@SuppressWarnings("serial")
public class InstanceDataModel<T> extends AbstractTableModel {
List<Attribute<? super T, ?>> attributes;
private T _instance;
public InstanceDataModel(EntityType<T> type, T instance) {
super();
attributes = MetamodelHelper.getAttributes(type);
this._instance = instance;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return columnIndex == 0 ? String.class : Object.class;
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public String getColumnName(int columnIndex) {
return columnIndex == 0 ? "Properties" : "Value";
}
@Override
public int getRowCount() {
return attributes.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Attribute<? super T, ?> attr = attributes.get(rowIndex);
return columnIndex == 0 ? attr.getName() : MetamodelHelper.getValue(attr, _instance);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public void setValueAt(Object value, int rowIndex, int columnIndex) {
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import javax.persistence.metamodel.EntityType;
import javax.swing.BorderFactory;
import javax.swing.JTable;
@SuppressWarnings("serial")
public class InstanceView<T> extends JTable {
public InstanceView(EntityType<T> type, T instance) {
super(new InstanceDataModel<T>(type, instance));
setShowHorizontalLines(false);
setShowVerticalLines(false);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
public interface Maze {
boolean isReachable(int x, int y);
}

View File

@ -0,0 +1,284 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.CollectionAttribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
/**
* Static utility for analyzing persistent metadata model using JPA 2.0.
*
* @author Pinaki Poddar
*
*/
public class MetamodelHelper {
public static final int ATTR_ID = 0;
public static final int ATTR_VERSION = 1;
public static final int ATTR_BASIC = 2;
public static final int ATTR_SINGULAR_RELATION = 3;
public static final int ATTR_PLURAL_RELATION = 4;
public static final Color MIDNIGHT_BLUE = new Color(25,25,112);
public static final Color DARK_GREEN = new Color(0,100,0);
public static final Color KHAKI = new Color(240, 230, 140);
public static String getDisplayName(Type<?> type) {
if (type instanceof EntityType) {
return getDisplayName((EntityType<?>)type);
}
return getDisplayName(type.getJavaType());
}
/**
* Gets the displayed name of a given entity type.
* @param type
* @return
*/
public static String getDisplayName(EntityType<?> type) {
return type.getName();
}
public static String getDisplayName(Class<?> cls) {
String fullName = cls.getName();
if (fullName.startsWith("java.") || fullName.startsWith("openbook.domain.")) {
int i = fullName.lastIndexOf('.');
return fullName.substring(i+1);
}
return fullName;
}
public static String getDisplayName(Attribute<?,?> attr) {
StringBuilder buffer = new StringBuilder();
buffer.append(getDisplayName(attr.getJavaType()));
if (attr instanceof MapAttribute) {
buffer.append("<").append(getDisplayName(((MapAttribute)attr).getKeyType())).append(",")
.append(getDisplayName(((MapAttribute)attr).getElementType())).append(">");
} else if (attr instanceof PluralAttribute) {
buffer.append("<").append(getDisplayName(((PluralAttribute)attr).getElementType())).append(">");
}
buffer.append(" ").append(attr.getName());
return buffer.toString();
}
public static <T> List<Attribute<? super T,?>> getAttributes(EntityType<T> type) {
List<Attribute<? super T,?>> list = new ArrayList<Attribute<? super T,?>>(type.getAttributes());
Collections.sort(list, new AttributeComparator());
return list;
}
public static int getAttributeType(Attribute<?, ?> a) {
if (a instanceof SingularAttribute) {
SingularAttribute<?, ?> sa = (SingularAttribute<?, ?>)a;
if (sa.isId()) return ATTR_ID;
if (sa.isVersion()) return ATTR_VERSION;
if (sa.isAssociation()) return ATTR_SINGULAR_RELATION;
if (sa.isCollection()) return ATTR_PLURAL_RELATION;
} else {
if (a.isAssociation()) return ATTR_SINGULAR_RELATION;
if (a.isCollection()) return ATTR_PLURAL_RELATION;
}
return ATTR_BASIC;
}
public static <T> Set<SingularAttribute<? super T, ?>> getIdAttributes(EntityType<T> type) {
Set<SingularAttribute<? super T,?>> attrs = type.getSingularAttributes();
Set<SingularAttribute<? super T,?>> idAttrs = new HashSet<SingularAttribute<? super T,?>>();
for (SingularAttribute<? super T,?> attr : attrs) {
if (attr.isId()) {
idAttrs.add(attr);
}
}
return idAttrs;
}
/**
* Finds the derived target of the given type, if any. Otherwise null.
*/
public static <T> EntityType<?> derivedTarget(EntityType<T> type) {
Set<SingularAttribute<? super T,?>> ids = getIdAttributes(type);
for (SingularAttribute<? super T,?> id : ids) {
EntityType<?> derived = getParentType(id);
if (derived != null) {
return derived;
}
}
return null;
}
public static EntityType<?> getParentType(SingularAttribute<?,?> id) {
if (id.getType() instanceof EntityType) {
return (EntityType<?>)id.getType();
}
return null;
}
public static boolean isId(Attribute<?,?> a) {
if (a instanceof SingularAttribute)
return ((SingularAttribute<?,?>)a).isId();
return false;
}
public static boolean isVersion(Attribute<?,?> a) {
if (a instanceof SingularAttribute)
return ((SingularAttribute<?,?>)a).isVersion();
return false;
}
public static Color getColor(Attribute<?,?> attr) {
if (isId(attr))
return Color.RED;
if (isVersion(attr))
return Color.DARK_GRAY;
return getColor(attr.getPersistentAttributeType());
}
public static Color getColor(Attribute.PersistentAttributeType type) {
switch (type) {
case BASIC : return Color.BLACK;
case EMBEDDED: return Color.GRAY;
case ONE_TO_ONE: return Color.BLUE;
case MANY_TO_ONE: return MIDNIGHT_BLUE;
case ONE_TO_MANY: return DARK_GREEN;
case MANY_TO_MANY: return Color.PINK;
case ELEMENT_COLLECTION: return KHAKI;
default: return Color.BLACK;
}
}
public static Integer getAttributeTypeCode(Attribute<?,?> attr) {
if (isId(attr))
return 0;
if (isVersion(attr))
return 1;
switch (attr.getPersistentAttributeType()) {
case BASIC :
case EMBEDDED:
return 2;
case ONE_TO_ONE:
case MANY_TO_ONE:
return 3;
case ONE_TO_MANY:
case MANY_TO_MANY:
case ELEMENT_COLLECTION: return 4;
default: return 5;
}
}
private static Map<Attribute<?,?>, Method> members = new HashMap<Attribute<?,?>, Method>();
private static Object[] EMPTY_ARGS = null;
private static Class<?>[] EMPTY_CLASSES = null;
/**
* Gets the value of the given persistent attribute for the given instance.
* @param attr
* @param instance
* @return
*/
public static Object getValue(Attribute<?,?> attr, Object instance) {
Member member = attr.getJavaMember();
Method getter = null;
if (member instanceof Method) {
getter = (Method)member;
} else if (members.containsKey(attr)) {
getter = members.get(attr);
} else {
getter = getMethod(attr.getDeclaringType().getJavaType(), attr.getName());
members.put(attr, getter);
}
if (getter == null)
return null;
try {
return getter.invoke(instance, EMPTY_ARGS);
} catch (Exception e) {
return null;
}
}
private static Method getMethod(Class<?> type, String p) {
try {
String getter = "get" + Character.toUpperCase(p.charAt(0))+p.substring(1);
return type.getMethod(getter, EMPTY_CLASSES);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Compares EntityType by their dependency of derived targets.
*
* @author Pinaki Poddar
*
*/
public static class EntityComparator implements Comparator<EntityType<?>> {
@Override
public int compare(EntityType<?> o1, EntityType<?> o2) {
if (derivedTarget(o1) == o2)
return 1;
if (derivedTarget(o2) == o1)
return -1;
return o1.getName().compareTo(o2.getName());
}
}
/**
* Compares attribute by their qualification.
* Identity
* Version
* Basic
* Singular association
* Plural association
*
* @author Pinaki Poddar
*
*/
public static class AttributeComparator implements Comparator<Attribute<?,?>> {
@Override
public int compare(Attribute<?, ?> a1, Attribute<?, ?> a2) {
Integer t1 = getAttributeTypeCode(a1);
Integer t2 = getAttributeTypeCode(a2);
if (t1.equals(t2)) {
return a1.getName().compareTo(a2.getName());
} else {
return t1.compareTo(t2);
}
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
/**
* Graphical View of a JPA 2.0 Metamodel.
* The view is isomorphic to the {@link Metamodel meta-model} defined by JPA 2.0 specification.
* Hence the view is organized in terms of corresponding views of {@link EntityTypeView entity}
* and their {@link AttributeView attributes}.
* <br>
* This view also draws linkage with A*-algorithm between the derived primary key attributes that
* reference other entity types.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class MetamodelView extends JPanel implements Maze {
private static final int GRID = 8;
int hgap = 40;
int vgap = 40;
/**
* Creates a panel where each {@link EntityTypeView} is placed in a FlowLayout.
* Only the entities of the model are ordered based on their primary key
* dependencies, if any.
*
* @param model
*/
@SuppressWarnings("unchecked")
public MetamodelView(Metamodel model) {
super(true);
FlowLayout layout = (FlowLayout)getLayout();
layout.setHgap(80);
layout.setVgap(80);
// getInsets(new Insets(100,100,100,100));
Set<EntityType<?>> types = model.getEntities();
List<EntityType<?>> sorted = new ArrayList<EntityType<?>>(types);
Collections.sort(sorted, new MetamodelHelper.EntityComparator());
for (EntityType type : sorted) {
EntityTypeView view = new EntityTypeView(type);
add(view);
}
}
EntityTypeView<?> findViewByType(EntityType<?> type) {
if (type == null)
return null;
for (Component c : getComponents()) {
if (c instanceof EntityTypeView) {
EntityTypeView<?> view = (EntityTypeView<?>)c;
if (view.getEntityType().equals(type)) {
return view;
}
}
}
return null;
}
@Override
public void paint(Graphics g) {
super.paint(g);
for (Component c : getComponents()) {
if (c instanceof EntityTypeView == false) {
continue;
}
EntityTypeView<?> view = (EntityTypeView<?>)c;
for (SingularAttribute<?,?> id : MetamodelHelper.getIdAttributes(view.getEntityType())) {
EntityTypeView<?> target = findViewByType(MetamodelHelper.getParentType(id));
if (target == null)
continue;
PathFinder runner = new PathFinder(this);
Point start = getConnectLocation(view, id);
Point finish = target.getLocation();
List<Point> path = runner.findPath(start.x/GRID, start.y/GRID,
finish.x/GRID, finish.y/GRID);
if (path.isEmpty())
continue;
Point p1 = path.get(0);
Point p2 = null;
for (int i = 1; i < path.size(); i++) {
p2 = path.get(i);
g.drawLine(p1.x*GRID, p1.y*GRID, p2.x*GRID, p2.y*GRID);
p1 = p2;
}
g.setColor(Color.BLACK);
int r = 4;
g.fillOval(p1.x*GRID -r, p1.y*GRID - r, 2*r, 2*r);
}
}
}
/**
* Gets the position of the attribute in the entity view relative to this panel.
* @param a
* @param attr
* @return
*/
Point getConnectLocation(EntityTypeView<?> a, Attribute<?,?> attr) {
Point p1 = a.getLocation();
Point p2 = a.getPosition(attr);
return new Point(p1.x + p2.x, p1.y + p2.y);
}
// contract for the maze
@Override
public boolean isReachable(int x, int y) {
for (Component view : getComponents()) {
Rectangle r = view.getBounds();
int px = x*GRID;
int py = y*GRID;
if (r.contains(px, py))
return false;
}
return true;
}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* A* Algorithm to find rectilinear path through a {@link Maze}.
*
* @author Pinaki Poddar
*
*/
public class PathFinder {
private final Maze _maze;
public PathFinder(Maze maze) {
_maze = maze;
}
/**
* A* algorithm to find a path through a maze.
* The algorithm follows these steps
* <LI> Add the starting square (or node) to the open list.
* <LI> Repeat the following:
* <LI> Current node is the lowest cost square on the open list
* <LI> Move current node to the closed list.
* <LI> For each of adjacent neighbor n to this current square
* <LI> If n is not {@link Maze#isReachable(int, int) reachable} or if n is on the closed list, ignore.
* Otherwise do the following.
* <LI> If n is not on the open list, add it to the open list. Make the current square
* the parent of n. Record the cost of n.
* <LI> If n is on the open list already, replace if this path to n is lower cost.
* until the target node is added to the closed list, or fail to find the target square
* i.e. the open list is empty.
*
* @param x1 the x-coordinate of the starting point
* @param y1 the y-coordinate of the starting point
* @param x2 the x-coordinate of the target point
* @param y2 the y-coordinate of the target point
* @return a array of points in the form of x1,y1, x2,y2, ....
*/
public List<Point> findPath(int x1, int y1, int x2, int y2) {
Node source = new Node(null, x1, y1);
Node target = new Node(null, x2, y2);
int maxCost = distance(source, target)*2;
LinkedList<Node> openList = new LinkedList<Node>();
List<Node> closedList = new ArrayList<Node>();
openList.add(source);
do {
Node current = openList.remove(0);
closedList.add(current);
if (current.f < maxCost) {
exploreNeighbours(current, target, openList, closedList);
}
} while (!openList.isEmpty() && findMatchingNode(x2, y2, closedList) == null);
target = findMatchingNode(x2, y2, closedList);
if (target == null)
return traceBackPath(closedList.get(closedList.size()-1));
return traceBackPath(target);
}
private void exploreNeighbours(Node current, Node target, List<Node> openList, List<Node> closedList) {
insertNeighbour(current, current.x+1, current.y, target, openList, closedList);
insertNeighbour(current, current.x-1, current.y, target, openList, closedList);
insertNeighbour(current, current.x, current.y+1, target, openList, closedList);
insertNeighbour(current, current.x, current.y-1, target, openList, closedList);
Collections.sort(openList);
}
private Node insertNeighbour(Node n, int x, int y, Node target, List<Node> openList, List<Node> closedList) {
if (distance(x,y,target) != 0) {
if (!_maze.isReachable(x, y) || findMatchingNode(x, y, closedList) != null) {
return null;
}
}
Node m = findMatchingNode(x, y, openList);
if (m == null) {
m = new Node(n,x,y);
m.g = n.g + 1;
m.h = distance(target, m);
m.f = m.g + m.h;
openList.add(m);
} else if (m.g > n.g+1){
m.parent = n;
m.g = n.g + 1;
m.f = m.g + m.h;
}
return m;
}
private Node findMatchingNode(int x, int y, List<Node> list) {
for (Node n : list) {
if (n.x == x && n.y == y)
return n;
}
return null;
}
int distance(Node n, Node m) {
return Math.abs(n.x - m.x) + Math.abs(n.y - m.y);
}
int distance(int x, int y, Node m) {
return Math.abs(x - m.x) + Math.abs(y - m.y);
}
List<Point> traceBackPath(Node target) {
LinkedList<Point> path = new LinkedList<Point>();
path.add(new Point(target.x, target.y));
Node next = target.parent;
while (next != null) {
path.add(0,new Point(next.x, next.y));
next = next.parent;
}
return straighten(path);
}
List<Point> straighten(List<Point> path) {
if (path.size() < 3)
return path;
List<Point> mids = new ArrayList<Point>();
Point prev = path.get(0);
Point mid = path.get(1);
for (int i = 2; i < path.size(); i++) {
Point next = path.get(i);
if ((mid.x == prev.x && mid.x == next.x) || (mid.y == prev.y && mid.y == next.y)) {
mids.add(mid);
}
prev = mid;
mid = next;
}
path.removeAll(mids);
return path;
}
private static class Node implements Comparable<Node> {
int f,g,h;
int x; int y;
Node parent;
public Node(Node p, int x, int y) {
parent = p;
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Node) {
Node that = (Node)o;
return x == that.x && y == that.y;
}
return false;
}
@Override
public int compareTo(Node o) {
if (f == o.f) return 0;
return f > o.f ? 1 : -1;
}
public String toString() {
return "(" + x + "," + y + ":" + g + ")";
}
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FilenameFilter;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import openbook.client.Images;
/**
* Displays and navigates PowerPoint slides.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class PowerPointViewer extends JPanel {
private List<WeakReference<ImageIcon>> _slides = new ArrayList<WeakReference<ImageIcon>>();
private int _total; // Total number of slides in the deck.
private String _dir;
private List<URL> _slideURLs;
private JButton _prev, _next;
private JSpinner _goto;
private JButton[] _navButtons;
private int MAX_BUTTONS = 10; // Total number of navigation buttons excluding the PREVIOUS and NEXT button.
// The slide
private JLabel _view;
// The key for client property in the navigation buttons denoting the 0-based index of the slides.
private static final String SLIDE_INDEX = "slide.index";
/**
* Create a viewer with slides in the specified directory.
*
* @param dir path to a directory containing PowerPoint slides.
* @param slides name of the slides
*/
public PowerPointViewer(String dir, List<String> slides) {
super(true);
_dir = dir.endsWith("/") ? dir : dir + "/";
_slideURLs = validateSlides(_dir, slides);
_total = _slideURLs.size();
for (int i = 0; i < _total; i++) {
_slides.add(null);
}
setLayout(new BorderLayout());
_view = new JLabel(getSlideAt(0));
add(new JScrollPane(_view), BorderLayout.CENTER);
add(createNavigationButtons(), BorderLayout.SOUTH);
}
/**
* Create buttons to navigate the slides.
*
* @return a panel containing the navigation buttons.
*/
private JPanel createNavigationButtons() {
JPanel buttons = new JPanel();
_navButtons = new JButton[Math.min(MAX_BUTTONS, _total)];
_prev = new JButton(Images.BACK);
buttons.add(_prev);
_prev.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
prevPage();
}
});
buttons.add(Box.createHorizontalGlue());
for (int i = 0; i < _navButtons.length; i++) {
if (i == _navButtons.length/2) {
JLabel gotoLabel = new JLabel("Go to ");
_goto = new JSpinner(new SpinnerNumberModel(1,1,_total,1));
buttons.add(gotoLabel);
buttons.add(_goto);
_goto.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
Integer page = (Integer)_goto.getValue();
showPage(page-1);
}
});
}
int slideIndex = i + 2;
_navButtons[i] = new JButton(""+slideIndex);
buttons.add(_navButtons[i]);
_navButtons[i].putClientProperty(SLIDE_INDEX, i+1);
_navButtons[i].addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JButton button = (JButton)e.getSource();
int index = (Integer)button.getClientProperty(SLIDE_INDEX);
showPage(index);
}
});
}
_next = new JButton(Images.NEXT);
buttons.add(Box.createHorizontalGlue());
buttons.add(_next);
_next.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
nextPage();
}
});
return buttons;
}
/**
* Show the next page.
*/
private void nextPage() {
int current = getCurrentPageIndex();
if (current >= _total-1)
return;
current += 1;
showPage(current);
}
private void prevPage() {
int current = getCurrentPageIndex();
if (current <= 0)
return;
current -= 1;
showPage(current);
}
int getCurrentPageIndex() {
return (Integer)_goto.getValue()-1;
}
/**
* Shows the slide at the given index.
*
* @param index 0-based index of the slides.
*/
private void showPage(int index) {
_view.setIcon(getSlideAt(index));
updateButtons(index);
}
/**
* Updates the buttons.
*
* @param current 0-based index of the currently displayed slide.
*/
private void updateButtons(int index) {
_goto.setValue(index+1);
int last = index + _navButtons.length;
if (last >= _total)
return;
for (int i = 0; i < _navButtons.length; i++) {
int slideIndex = index+i+2;
_navButtons[i].setText(""+ slideIndex);
_navButtons[i].putClientProperty(SLIDE_INDEX, (index+i+1));
}
}
public int getSlideCount() {
return _total;
}
public ImageIcon getSlideAt(int index) {
WeakReference<ImageIcon> weak = _slides.get(index);
if (weak == null) {
return loadSlide(index);
}
return (weak.get() == null) ? loadSlide(index) : weak.get();
}
ImageIcon loadSlide(int index) {
URL imgURL = _slideURLs.get(index);
ImageIcon icon = new ImageIcon(imgURL);
_slides.add(index, new WeakReference<ImageIcon>(icon));
return icon;
}
List<URL> validateSlides(String dir, List<String> slides) {
List<URL> valid = new ArrayList<URL>();
for (String slide : slides) {
URL url = findResource(dir + slide);
if (url != null) {
valid.add(url);
}
}
return valid;
}
private URL findResource(String path) {
if (path == null)
return null;
URL imgURL = Thread.currentThread().getContextClassLoader().getResource(path);
if (imgURL == null) {
imgURL = getClass().getResource(path);
if (imgURL == null) {
System.err.println("Couldn't find file: " + path);
return null;
}
}
return imgURL;
}
// private ImageIcon createImageIcon(String path) {
// if (path == null)
// return null;
// URL imgURL = Thread.currentThread().getContextClassLoader().getResource(path);
// if (imgURL != null) {
// return new ImageIcon(imgURL);
// } else {
// imgURL = Images.class.getResource(path);
// if (imgURL != null) {
// return new ImageIcon(imgURL);
// } else {
// System.err.println("Couldn't find file: " + path);
// return null;
// }
// }
// }
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import javax.swing.JTable;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
/**
* A specialized table to display statistics of queries.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class PreparedQueryViewer extends JTable {
public PreparedQueryViewer(OpenJPAEntityManagerFactory emf) {
super(new QueryStatisticsModel(((OpenJPAEntityManagerFactorySPI)emf).getConfiguration()
.getQuerySQLCacheInstance().getStatistics()));
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import javax.persistence.Query;
/**
* Decorates an executable query.
* Concrete decorative action can be binding parameters, limiting the result range etc.
*
* @author Pinaki Poddar
*
*/
public interface QueryDecorator {
void decorate(Query q);
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;
import org.apache.openjpa.kernel.QueryStatistics;
@SuppressWarnings("serial")
class QueryStatisticsModel extends AbstractTableModel {
QueryStatistics<String> _stats;
private List<String> _keys = new ArrayList<String>();
QueryStatisticsModel(QueryStatistics<String> stats) {
_stats = stats;
sortKeys(stats);
}
@Override
public int getColumnCount() {
return 3;
}
@Override
public int getRowCount() {
sortKeys(_stats);
return _keys.size();
}
@Override
public String getColumnName(int columnIndex) {
switch (columnIndex) {
case 0: return "Total";
case 1: return "Hit";
case 2: return "Query";
default : return "null";
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
String query = _keys.get(rowIndex);
switch (columnIndex) {
case 0: return _stats.getExecutionCount(query);
case 1: return _stats.getHitCount(query);
case 2: return query;
default : return null;
}
}
void sortKeys(QueryStatistics<String> stats) {
if (_stats.keys().size() != _keys.size()) {
_keys = new ArrayList<String>(_stats.keys());
if (_keys.size() > 1) {
Collections.sort(_keys);
}
fireTableDataChanged();
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.io.PrintStream;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import jpa.tools.swing.GraphicOutputStream;
import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
import org.apache.openjpa.lib.jdbc.JDBCEvent;
/**
* Logs SQL statement to a graphic console.
*
* @author Pinaki Poddar
*
*/
public class SQLLogger extends AbstractJDBCListener {
private static PrintStream out = null;
private static AttributeSet red, green, blue, magenta;
static {
StyleContext ctx = StyleContext.getDefaultStyleContext();
red = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED);
green = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.GREEN);
blue = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.BLUE);
magenta = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.MAGENTA);
}
public static void setOutput(GraphicOutputStream o) {
out = new PrintStream(o, true);
o.registerStyle("INSERT", green);
o.registerStyle("SELECT", blue);
o.registerStyle("UPDATE", magenta);
o.registerStyle("DELETE", red);
}
@Override
public void beforeExecuteStatement(final JDBCEvent event) {
if (out == null)
return;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
out.println(event.getSQL());
}
});
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
/**
* A TextPane where text that scrolls as new text is appended.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class ScrollingTextPane extends JTextPane {
public void appendText(String text) {
if (text == null)
return;
try {
Document doc = getDocument();
setCaretPosition(doc.getLength());
replaceSelection(text);
Rectangle r = modelToView(doc.getLength());
if (r != null) {
scrollRectToVisible(r);
}
} catch (Exception e) {
}
}
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ScrollingTextPane test = new ScrollingTextPane();
f.getContentPane().add(new JScrollPane(test));
f.pack();
f.setSize(600,450);
f.setVisible(true);
Timer timer = new Timer(1000, new ActionListener() {
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss");
@Override
public void actionPerformed(ActionEvent e) {
test.appendText(fmt.format(new Date()) + "\r\n");
}
});
timer.start();
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
/**
* A viewer for source code.
* The input to this viewer is a set of *.html file names which may or may not have anchors.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class SourceCodeViewer extends JPanel implements ActionListener {
private final JEditorPane _editor;
private final JComboBox _bookmarks;
private Map<String, File> _files = new TreeMap<String, File>();
public SourceCodeViewer(String dir) {
super(true);
setLayout(new BorderLayout());
_editor = new JEditorPane();
_editor.setEditorKit(new HTMLEditorKit());
_editor.setEditable(false);
File root = getFile(dir);
if (root != null) {
List<File> files = new FileScanner("html", true).scan(root);
for (File f : files) {
String fileName = f.getAbsolutePath();
String key = fileName.substring(
root.getAbsolutePath().length()+1,
fileName.length()- "html".length()-1)
.replace(File.separatorChar,'.');
_files.put(key, f);
}
}
DefaultComboBoxModel model = new DefaultComboBoxModel();
for (String key : _files.keySet()) {
model.addElement(key);
}
_bookmarks = new JComboBox(model);
_editor.setEditable(false);
_bookmarks.setEditable(false);
_bookmarks.addActionListener(this);
add(new JScrollPane(_editor), BorderLayout.CENTER);
JPanel topPanel = new JPanel();
((FlowLayout)topPanel.getLayout()).setAlignment(FlowLayout.LEADING);
topPanel.add(new JLabel("Select File "));
topPanel.add(_bookmarks);
topPanel.add(Box.createHorizontalGlue());
add(topPanel, BorderLayout.NORTH);
}
public void actionPerformed(ActionEvent e) {
String name = (String)_bookmarks.getSelectedItem();
try {
HTMLDocument doc = new HTMLDocument();
_editor.read(new FileInputStream(_files.get(name)), doc);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Check the given string for a matching file. The string is first
* tested to see if it is an existing file path. If it does not
* represent an existing file, it is checked as a resource name of a
* file.
* @param name the file path or resource name
*/
private File getFile(String name) {
if (name == null)
return null;
File file = new File(name);
if (file.exists())
return file;
URL url = Thread.currentThread().getContextClassLoader().getResource(name);
if (url == null) {
url = getClass().getClassLoader().getResource(name);
}
if (url != null) {
String urlFile = url.getFile();
if (urlFile != null) {
File rsrc = new File(URLDecoder.decode(urlFile));
if (rsrc.exists())
return rsrc;
}
}
return null;
}
InputStream getResource(String resource) {
InputStream stream = getClass().getResourceAsStream(resource);
if (stream == null) {
stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
}
return stream;
}
public static void main(String[] args) throws Exception {
SourceCodeViewer viewer = new SourceCodeViewer(args.length == 0 ? "source" : args[0]);
JFrame frame = new JFrame("Source Code Viewer");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(viewer);
frame.pack();
frame.setVisible(true);
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.TimeUnit;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.Timer;
@SuppressWarnings("serial")
public class StatusBar extends JPanel implements PropertyChangeListener {
private JProgressBar progressBar;
private JProgressBar memoryBar;
private JLabel messageText;
private TaskProgress task;
public StatusBar() {
progressBar = new JProgressBar();
memoryBar = new JProgressBar();
messageText = new JLabel();
setLayout(new GridLayout(1,0));
add(messageText);
add(Box.createHorizontalGlue());
add(memoryBar);
add(new JLabel("Total " + (Runtime.getRuntime().maxMemory()/1000000) + "MB"));
add(progressBar);
MemoryDisplay memory = new MemoryDisplay(memoryBar);
new Timer(100, memory).start();
}
public void showMessage(String text) {
messageText.setText(text);
}
public void startTimer(long duration, int interval, TimeUnit unit) {
progressBar.setEnabled(true);
if (duration > 0) {
progressBar.setStringPainted(true);
progressBar.setMaximum(100);
progressBar.setMinimum(0);
task = new TaskProgress(unit.toMillis(duration), interval);
task.addPropertyChangeListener(this);
task.execute();
} else {
progressBar.setStringPainted(false);
progressBar.setIndeterminate(true);
task = new TaskProgress(duration, interval);
task.addPropertyChangeListener(this);
task.execute();
}
}
/**
* Invoked when task's progress property changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
public void stopTimer() {
if (task != null) {
task.cancel(true);
task = null;
}
progressBar.setIndeterminate(false);
progressBar.setString("");
progressBar.setEnabled(false);
}
/*
* Emits progress property from a background thread.
*/
class TaskProgress extends SwingWorker<Void, Integer> {
private long startTimeInMillis;
private long _total = 100;
private int _interval = 100;
public TaskProgress(long total, int interval) {
_total = Math.max(total, 1);
_interval = Math.max(interval, 1);
}
@Override
public Void doInBackground() {
startTimeInMillis = System.currentTimeMillis();
long endTimeInMillis = startTimeInMillis + _total;
long current = System.currentTimeMillis();
while (current < endTimeInMillis && !isCancelled()) {
try {
current = System.currentTimeMillis();
int pctComplete = (int)((100*(current - startTimeInMillis))/_total);
setProgress(pctComplete);
Thread.sleep(_interval);
} catch (InterruptedException ignore) {
}
}
return null;
}
}
public class MemoryDisplay implements ActionListener {
JProgressBar bar;
public MemoryDisplay(JProgressBar bar) {
this.bar = bar;
bar.setStringPainted(true);
bar.setMinimum(0);
bar.setMaximum(100);
}
@Override
public void actionPerformed(ActionEvent e) {
Runtime jvm = Runtime.getRuntime();
long totalMemory = jvm.totalMemory();
long usedMemory = totalMemory-jvm.freeMemory();
int usedPct = (int)((100*usedMemory)/totalMemory);
bar.setForeground(getColor(usedPct));
bar.setValue((int)usedPct);
bar.setString(usedPct + "% (" + mb(usedMemory) + "/" + mb(totalMemory) + "MB) ");
}
private long mb(long m) {
return m/1000000;
}
Color getColor(int pct) {
int red = 255*pct/100;
int green = 255*(100-pct)/100;
return new Color(red, green, 0);
}
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.AWTException;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
/**
* Assorted utility methods.
*
* @author Pinaki Poddar
*
*/
public class SwingHelper {
/**
* Position the given component at the center of the given parent component or physical screen.
*
* @param c the component to be positioned
* @param parent the component whose center will match the center of the given component.
* If null, the given component will match the screen center.
*
*/
public static void position(Component c, Component parent) {
Dimension d = c.getPreferredSize();
if (parent == null) {
Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
c.setLocation(s.width/2 - d.width/2, s.height/2 - d.height/2);
} else {
Point p = parent.getLocationOnScreen();
int pw = parent.getWidth();
int ph = parent.getHeight();
c.setLocation(p.x + pw/2 - d.width/2, p.y + ph/2 - d.height/2);
}
}
private static int[] factors = {1000, 1000, 1000, 1000, 60, 60, 24};
public static String getTimeString(long value, TimeUnit unit) {
if (value <= 0)
return "";
int i = unit.ordinal();
TimeUnit[] units = TimeUnit.values();
TimeUnit next = null;
int factor = -1;
if (i < factors.length -1) {
next = units[i+1];
factor = factors[i+1];
long nextValue = value/factor;
if (nextValue > 0)
return getTimeString(value/factor, next) + " " + getTimeString(value%factor, unit);
}
return value + toString(unit);
}
public static String toString(TimeUnit unit) {
switch (unit) {
case HOURS:
case DAYS:
case MINUTES:
case SECONDS:
return unit.toString().substring(0,1).toLowerCase();
case MILLISECONDS:
return "ms";
case MICROSECONDS:
return "micros";
case NANOSECONDS:
return "ns";
}
return "";
}
public static void print(Component c, String format, File output) {
try {
Robot robot = new Robot();
Point root = c.getLocationOnScreen();
BufferedImage shot = robot.createScreenCapture(new Rectangle(root.x, root.y, c.getWidth(), c.getHeight()));
ImageIO.write(shot, format, output);
} catch (AWTException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static AbstractButton getSelectedButton(ButtonGroup group) {
Enumeration<AbstractButton> buttons = group.getElements();
while (buttons.hasMoreElements()) {
AbstractButton b = buttons.nextElement();
if (b.isSelected()) {
return b;
}
}
return null;
}
public static void setLookAndFeel(int fontSize) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
UIDefaults defaults = UIManager.getDefaults();
Enumeration<Object> keys = defaults.keys();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
if ((key instanceof String) && (((String) key).endsWith(".font"))) {
FontUIResource font = (FontUIResource) UIManager.get(key);
defaults.put (key, new FontUIResource(font.getFontName(), font.getStyle(), fontSize));
}
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 jpa.tools.swing;
import java.awt.Color;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
public class TextStyles {
public static AttributeSet KEYS, VALUES;
static {
StyleContext ctx = StyleContext.getDefaultStyleContext();
KEYS = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.BLUE);
KEYS = ctx.addAttribute(KEYS, StyleConstants.Bold, true);
KEYS = ctx.addAttribute(KEYS, StyleConstants.FontSize, 14);
KEYS = ctx.addAttribute(KEYS, StyleConstants.FontFamily, "Courier");
Color indianRed = new Color(205, 92, 92);
VALUES = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, indianRed);
VALUES = ctx.addAttribute(VALUES, StyleConstants.Bold, true);
VALUES = ctx.addAttribute(VALUES, StyleConstants.FontFamily, "Courier");
VALUES = ctx.addAttribute(VALUES, StyleConstants.FontSize, 14);
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Swing widgets to browse persistent entities using JPA 2.0 MetaModel API.
</body>
</html>

View File

@ -0,0 +1,459 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import jpa.tools.swing.EntityDataModel;
import jpa.tools.swing.EntityTable;
import jpa.tools.swing.EntityTableView;
import jpa.tools.swing.ErrorDialog;
import openbook.domain.Author;
import openbook.domain.Book;
import openbook.domain.Customer;
import openbook.domain.PurchaseOrder;
import openbook.domain.ShoppingCart;
import openbook.server.OpenBookService;
import openbook.server.QueryDecorator;
import org.apache.openjpa.lib.jdbc.SQLFormatter;
/**
* A visual page coordinates the following functions of {@link OpenBookService} :
* <li>query for books
* <li>choose one or more of the selected books
* <li>add them to Shopping Cart
* <li>place a purchase order of the books in the shopping cart.
* <p>
* Each interaction with the underlying service occurs in a background i.e.
* a <em>non</em>-AWT event dispatching thread. The background threads are
* used via {@link SwingWorker} and hence each persistence operation is
* can be potentially handled by a different JPA persistence context.
* This threading model not only adheres to the good practice of responsive graphical
* user interface design, it exercises the <em>remote</em> nature of JPA service
* (even within this single process Swing application) where every operation
* on a persistence context results into a set of <em>detached</em> instances
* to the remote client.
*
* @author Pinaki Poddar
*/
@SuppressWarnings("serial")
public final class BuyBookPage extends JPanel {
private final OpenBookService _service;
private final Customer _customer;
private final SearchPanel _searchPanel;
private final BuyPanel _buyPanel;
private final SelectBookPanel _selectPanel;
private final ShoppingCartPanel _cartPanel;
/**
* A Page with 2x2 Grid of panels each for one of the specific action.
*
* @param service the OpenBooks service handle.
*/
public BuyBookPage(OpenBookService service, Customer customer) {
super(true);
_service = service;
_customer = customer;
GridLayout gridLayout = new GridLayout(2, 2);
this.setLayout(gridLayout);
_searchPanel = new SearchPanel("Step 1: Search for Books");
_selectPanel = new SelectBookPanel("Step 2: Select Books to view details");
_buyPanel = new BuyPanel("Step 3: Add Book to Shopping Cart");
_cartPanel = new ShoppingCartPanel("Step 4: Purchase Books in the Shopping Cart");
add(_searchPanel);
add(_cartPanel);
add(_selectPanel);
add(_buyPanel);
_selectPanel._selectedBooks.getTable()
.getSelectionModel()
.addListSelectionListener(_buyPanel);
}
/**
* A form like panel displays the different fields to search for books.
* Zero or more form fields can be filled in. Though the service level
* contract does not mandate how to form the exact query from the form
* field values, the actual implementation demonstrates how dynamic
* query construction introduced via Criteria Query API in JPA 2.0
* can aid in such common user scenarios.
* <br>
* The object level query is displayed to demonstrate the ability of
* OpenJPA to express a dynamic Criteria Query is a human-readable,
* JPQL-like query string.
*
* @author Pinaki Poddar
*
*/
class SearchPanel extends JPanel implements ActionListener {
private final JTextField _title = new JTextField("", 20);
private final JTextField _author = new JTextField("", 20);
private final JTextField _maxPrice = new JTextField("", 6);
private final JTextField _minPrice = new JTextField("", 6);
private final JTextArea _queryView = new JTextArea();
private final SQLFormatter _formatter = new SQLFormatter();
SearchPanel(String title) {
super(true);
setBorder(BorderFactory.createTitledBorder(title));
JButton searchButton = new JButton("Search", Images.START);
searchButton.setHorizontalTextPosition(SwingConstants.LEADING);
JLabel titleLabel = new JLabel("Title :", SwingConstants.RIGHT);
JLabel authorLabel = new JLabel("Author:", SwingConstants.RIGHT);
JLabel priceLabel = new JLabel("Price :", SwingConstants.RIGHT);
JLabel fromLabel = new JLabel("from ", SwingConstants.RIGHT);
JLabel toLabel = new JLabel("to ", SwingConstants.RIGHT);
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateContainerGaps(true);
layout.setAutoCreateGaps(true);
GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
hGroup.addGroup(layout.createParallelGroup()
.addComponent(titleLabel)
.addComponent(authorLabel)
.addComponent(priceLabel));
hGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(_title)
.addComponent(_author)
.addGroup(layout.createSequentialGroup()
.addComponent(fromLabel)
.addComponent(_minPrice)
.addComponent(toLabel)
.addComponent(_maxPrice)));
GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
vGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(titleLabel)
.addComponent(_title));
vGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(authorLabel)
.addComponent(_author));
vGroup.addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER)
.addComponent(priceLabel)
.addComponent(fromLabel)
.addComponent(_minPrice)
.addComponent(toLabel)
.addComponent(_maxPrice));
layout.setHorizontalGroup(hGroup);
layout.setVerticalGroup(vGroup);
JPanel buttonPanel = new JPanel();
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(searchButton);
buttonPanel.add(Box.createHorizontalGlue());
BoxLayout box = new BoxLayout(this, BoxLayout.Y_AXIS);
setLayout(box);
add(panel);
add(Box.createVerticalGlue());
add(buttonPanel);
_queryView.setBorder(BorderFactory.createTitledBorder("Criteria Query as CQL"));
_queryView.setWrapStyleWord(true);
_queryView.setEditable(false);
_queryView.setBackground(getBackground());
add(_queryView);
searchButton.addActionListener(this);
}
/**
* Execute a query and displays the result onto {@linkplain SelectBookPanel}.
*
* The query is executed in a background, non-AWT thread.
*/
public void actionPerformed(ActionEvent e) {
new SwingWorker<List<Book>, Void>() {
private String queryString;
@Override
protected List<Book> doInBackground() throws Exception {
queryString = _service.getQuery(_title.getText(),
asDouble(_minPrice), asDouble(_maxPrice),
_author.getText());
return _service.select(_title.getText(),
asDouble(_minPrice), asDouble(_maxPrice),
_author.getText(),
(QueryDecorator[])null);
}
@Override
protected void done() {
try {
_queryView.setText(_formatter.prettyPrint(queryString).toString());
List<Book> selectedBooks = get(1, TimeUnit.SECONDS);
_selectPanel.updateDataModel(selectedBooks);
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
}
}
}.execute();
}
boolean isEmpty(JTextField text) {
if (text == null)
return true;
String s = text.getText();
return s == null || s.isEmpty() || s.trim().isEmpty();
}
Double asDouble(JTextField field) {
if (isEmpty(field)) return null;
try {
return Double.parseDouble(field.getText());
} catch (NumberFormatException e) {
System.err.println("Can not convert [" + field.getText() + "] to a double");
}
return null;
}
}
/**
* A panel to display the selected books in a tabular format.
*
* @author Pinaki Poddar
*
*/
class SelectBookPanel extends JPanel {
private final JLabel _bookCount;
private final EntityTableView<Book> _selectedBooks;
SelectBookPanel(String title) {
setLayout(new BorderLayout());
setBorder(BorderFactory.createTitledBorder(title));
_selectedBooks = new EntityTableView<Book>(Book.class,
EntityDataModel.BASIC_ATTR | EntityDataModel.ROW_COUNT,
_service.getUnit());
_bookCount = new JLabel();
add(_bookCount, BorderLayout.NORTH);
add(_selectedBooks, BorderLayout.CENTER);
}
void updateDataModel(List<Book> books) {
_bookCount.setText(books.size() + " Book selected");
_selectedBooks.getDataModel().updateData(books);
}
}
/**
* A panel to display the details of a single book and a button to add the
* book to cart. Listens to the selection in the selected books.
*
* @author Pinaki Poddar
*
*/
class BuyPanel extends JPanel implements ListSelectionListener {
JLabel _bookTitle;
JLabel _bookAuthors;
JLabel _bookPrice;
JLabel _bookISBN;
JButton _addToCart;
JSpinner _quantity;
JPanel _quantityPanel;
public BuyPanel(String title) {
super(true);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(BorderFactory.createTitledBorder(title));
JPanel descPanel = new JPanel();
descPanel.setLayout(new BoxLayout(descPanel, BoxLayout.Y_AXIS));
_bookTitle = new JLabel();
_bookAuthors = new JLabel();
_bookPrice = new JLabel();
_bookISBN = new JLabel();
descPanel.add(_bookTitle);
descPanel.add(_bookAuthors);
descPanel.add(_bookPrice);
descPanel.add(_bookISBN);
add(descPanel);
_quantityPanel = new JPanel();
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(1, 1, 10, 1);
_quantity = new JSpinner(spinnerModel);
_quantityPanel.add(new JLabel("Quantity:"));
_quantity.setEnabled(false);
_quantityPanel.add(_quantity);
_quantityPanel.setVisible(false);
add(_quantityPanel);
add(Box.createVerticalGlue());
JPanel buttonPanel = new JPanel();
buttonPanel.add(Box.createHorizontalGlue());
_addToCart = new JButton("Add to Cart", Images.getIcon("images/Add2Cart.jpg", true));
_addToCart.setEnabled(false);
buttonPanel.add(_addToCart);
buttonPanel.add(Box.createHorizontalGlue());
add(buttonPanel);
_addToCart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
_cartPanel.addBook((Book)_addToCart.getClientProperty("Book"),
(Integer)_quantity.getValue());
}
});
}
void showBookDetails(Book book) {
_bookTitle.setText(book.getTitle());
List<Author> authors = book.getAuthors();
if (authors != null && !authors.isEmpty()) {
StringBuilder names = new StringBuilder();
for (Author author : authors) {
if (names.length() != 0) names.append(", ");
names.append(author.getName());
}
_bookAuthors.setText("by " + names);
}
_bookPrice.setText("Price:" + book.getPrice());
_bookISBN.setText("ISBN: " + book.getISBN());
_addToCart.setEnabled(true);
_quantity.setEnabled(true);
_quantityPanel.setVisible(true);
_addToCart.putClientProperty("Book", book);
}
@Override
public void valueChanged(ListSelectionEvent e) {
EntityTable<Book> table = _selectPanel._selectedBooks.getTable();
int row = table.getSelectedRow();
if (row == -1)
return;
int col = table.getSelectedColumn();
if (col == -1)
return;
EntityDataModel<Book> model = (EntityDataModel<Book>) table.getModel();
Book book = model.getRow(row);
showBookDetails(book);
}
}
/**
* A panel to display the shopping cart.
*
* @author Pinaki Poddar
*
*/
class ShoppingCartPanel extends JPanel implements ActionListener {
private static final int MAX_ITEMS = 10;
private final ShoppingCart _cart;
private final JButton _placeOrder;
private final JLabel[] _items = new JLabel[MAX_ITEMS];
public ShoppingCartPanel(String title) {
_cart = _customer.newCart();
setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
setBorder(BorderFactory.createTitledBorder(title));
_placeOrder = new JButton("Place Order", Images.START);
_placeOrder.setHorizontalTextPosition(SwingConstants.LEADING);
_placeOrder.setEnabled(false);
for (int i = 0; i < MAX_ITEMS; i++) {
_items[i] = new JLabel("");
add(_items[i]);
}
add(Box.createVerticalGlue());
JPanel buttonPanel = new JPanel();
buttonPanel.add(Box.createHorizontalGlue());
buttonPanel.add(_placeOrder);
buttonPanel.add(Box.createHorizontalGlue());
add(buttonPanel);
_placeOrder.addActionListener(this);
}
/**
* Add the given book to the cart. Updates the display.
*/
public void addBook(Book book, int quantity) {
_cart.addItem(book, quantity);
updateDisplay();
}
void updateDisplay() {
Map<Book, Integer> items = _cart.getItems();
int i = 0;
for (Map.Entry<Book, Integer> entry : items.entrySet()) {
JLabel item = _items[i++];
int quantity = entry.getValue();
Book book = entry.getKey();
item.setText(quantity + (quantity == 1 ? " copy of " : " copies of ") + book.getTitle());
item.setIcon(Images.DONE);
}
_placeOrder.setEnabled(items.size()>0);
super.repaint();
}
@Override
public void actionPerformed(ActionEvent e) {
new SwingWorker<PurchaseOrder, Void>() {
@Override
protected PurchaseOrder doInBackground() throws Exception {
return _service.placeOrder(_cart);
}
@Override
protected void done() {
try {
get(1, TimeUnit.SECONDS);
_cart.clear();
for (int i = 0; i < MAX_ITEMS; i++) {
_items[i].setText("");
_items[i].setIcon(null);
}
_placeOrder.setEnabled(false);
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
}
}
}.execute();
}
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingWorker;
import jpa.tools.swing.EntityDataModel;
import jpa.tools.swing.EntityTableView;
import jpa.tools.swing.ErrorDialog;
import openbook.domain.PurchaseOrder;
import openbook.server.OpenBookService;
/**
* A page to control delivery of pending orders.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class DeliveryPage extends JPanel {
private final JButton _deliver;
private EntityTableView<PurchaseOrder> _orders;
private final OpenBookService _service;
private final JLabel _title;
private final JRadioButton _showPending;
private final JRadioButton _showDelivered;
private final JRadioButton _showBoth;
public DeliveryPage(final OpenBookService service) {
setLayout(new BorderLayout());
_service = service;
_orders = new EntityTableView<PurchaseOrder>(PurchaseOrder.class, getOrders(PurchaseOrder.Status.PENDING),
EntityDataModel.ALL_ATTR, service.getUnit());
_title = new JLabel(_orders.getDataModel().getRowCount() + " " + PurchaseOrder.Status.PENDING
+ " PurchaseOrder");
_deliver = new JButton("Deliver Pending Orders");
JPanel statusPanel = new JPanel();
ButtonGroup statusSelection = new ButtonGroup();
_showPending = new JRadioButton("Pending");
_showDelivered = new JRadioButton("Delivered");
_showBoth = new JRadioButton("All");
_showPending.setSelected(true);
statusSelection.add(_showPending);
statusSelection.add(_showDelivered);
statusSelection.add(_showBoth);
statusPanel.add(_showBoth);
statusPanel.add(_showPending);
statusPanel.add(_showDelivered);
JPanel actionPanel = new JPanel();
actionPanel.add(_deliver);
add(statusPanel, BorderLayout.NORTH);
add(_orders, BorderLayout.CENTER);
add(actionPanel, BorderLayout.SOUTH);
_showBoth.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<PurchaseOrder> selected = getOrders(null);
_orders.getDataModel().updateData(selected);
_title.setText(selected.size() + " PurchaseOrder");
}
});
_showPending.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<PurchaseOrder> selected = getOrders(PurchaseOrder.Status.PENDING);
_orders.getDataModel().updateData(selected);
_title.setText(selected.size() + " Pending PurchaseOrder");
}
});
_showDelivered.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<PurchaseOrder> selected = getOrders(PurchaseOrder.Status.DELEVERED);
_orders.getDataModel().updateData(selected);
_title.setText(selected.size() + " Delivered PurchaseOrder");
}
});
_deliver.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new SwingWorker<List<PurchaseOrder>, Void>() {
@Override
protected List<PurchaseOrder> doInBackground() throws Exception {
List<PurchaseOrder> updated = new ArrayList<PurchaseOrder>();
EntityDataModel<PurchaseOrder> orders = _orders.getDataModel();
int n = orders.getRowCount();
for (int i = 0; i < n; i++) {
PurchaseOrder order = orders.getRow(i);
if (order.getStatus() == PurchaseOrder.Status.PENDING) {
updated.add(_service.deliver(order));
}
}
return updated;
}
@Override
public void done() {
try {
List<PurchaseOrder> updated = get(10, TimeUnit.SECONDS);
_orders.getDataModel().updateData(updated);
_title.setText(updated.size() + "" + " Updated PurchaseOrder");
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
}
}
}.execute();
}
});
}
/**
* Gets the orders in a background (i.e. not AWT event dispatch thread)
* thread. <br>
* But blocks painting anyway, because that is what is intended.
*
*/
private List<PurchaseOrder> getOrders(final PurchaseOrder.Status status) {
SwingWorker<List<PurchaseOrder>, Void> worker = new SwingWorker<List<PurchaseOrder>, Void>() {
@Override
protected List<PurchaseOrder> doInBackground() throws Exception {
return _service.getOrders(status);
}
};
worker.execute();
try {
return worker.get();
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,634 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import jpa.tools.swing.AttributeLegendView;
import jpa.tools.swing.ConfigurationViewer;
import jpa.tools.swing.ErrorDialog;
import jpa.tools.swing.GraphicOutputStream;
import jpa.tools.swing.MetamodelView;
import jpa.tools.swing.PowerPointViewer;
import jpa.tools.swing.PreparedQueryViewer;
import jpa.tools.swing.ScrollingTextPane;
import jpa.tools.swing.SourceCodeViewer;
import jpa.tools.swing.StatusBar;
import jpa.tools.swing.SwingHelper;
import openbook.domain.Customer;
import openbook.server.OpenBookService;
import openbook.server.ServiceFactory;
import openbook.util.PropertyHelper;
import org.apache.openjpa.conf.OpenJPAVersion;
import org.apache.openjpa.persistence.OpenJPAPersistence;
/**
* A graphical user interface based client of OpenBooks for demonstration.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class Demo extends JFrame implements Thread.UncaughtExceptionHandler {
private static final Dimension TAB_VIEW = new Dimension(1400,800);
private static final Dimension OUT_VIEW = new Dimension(1400,200);
private static final Dimension NAV_VIEW = new Dimension(400,1000);
/**
* The actions invoked by this sample demonstration.
*/
private Action _root;
private Action _about;
private Action _buyBook;
private Action _deliver;
private Action _supply;
private Action _viewConfig;
private Action _viewDomain;
private Action _viewData;
private Action _viewSource;
private Action _viewQuery;
/**
* The primary graphic widgets used to invoke and display the results of the actions.
*/
private JToolBar _toolBar;
private JTree _navigator;
private JTabbedPane _tabbedPane;
private JTabbedPane _outputPane;
private StatusBar _statusBar;
private ScrollingTextPane _sqlLog;
public static final Icon LOGO = Images.getIcon("images/OpenBooks.jpg");
private boolean _debug = Boolean.getBoolean("openbook.debug");
/**
* The handle to the service.
*/
private OpenBookService _service;
private Customer _customer;
private Map<String, Object> _config;
/**
* Runs the demo.
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
SwingHelper.setLookAndFeel(14);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Demo demo = new Demo();
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demo.pack();
SwingHelper.position(demo, null);
demo.setVisible(true);
}
});
}
private Demo() {
Thread.currentThread().setUncaughtExceptionHandler(this);
_config = PropertyHelper.load(System.getProperty("openbook.client.config", "demo.properties"));
setTitle("OpenBooks: A Sample JPA 2.0 Application");
_root = new WelcomeAction("OpenBooks", "images/OpenBooks.jpg", "OpenBooks");
_about = new AboutAction("About OpenBooks", "images/OpenBooks.jpg", "About OpenBooks");
_buyBook = new BuyBookAction("Buy", "images/Add2Cart.jpg", "Browse and Buy Books");
_deliver = new DeliveryAction("Deliver", "images/Deliver.jpg", "Deliver Pending Orders");
_supply = new SupplyAction("Supply", "images/Supply.jpg", "Supply Books");
_viewConfig = new ViewConfigAction("Configuration", "images/browse.png", "View Configuration");
_viewDomain = new ViewDomainAction("Domain", "images/DomainModel.jpg", "View Domain Model");
_viewData = new ViewDataAction("Data", "images/DataModel.jpg", "View Instances");
_viewSource = new ViewSourceAction("Source", "images/SourceCode.jpg", "View Source Code");
_viewQuery = new ViewQueryCacheAction("Query", "images/DataModel.jpg", "View Queries");
_toolBar = createToolBar();
_navigator = createNavigator();
_tabbedPane = createTabbedView();
_outputPane = createOutputView();
_statusBar = createStatusBar();
JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
horizontalSplitPane.setContinuousLayout(true);
horizontalSplitPane.setDividerSize(1);
JScrollPane scrollPane = new JScrollPane(_navigator);
scrollPane.setMinimumSize(new Dimension(NAV_VIEW.width/4, NAV_VIEW.height));
scrollPane.setPreferredSize(NAV_VIEW);
horizontalSplitPane.add(scrollPane);
JSplitPane verticalSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
verticalSplitPane.setContinuousLayout(true);
verticalSplitPane.setDividerSize(1);
verticalSplitPane.add(_tabbedPane);
verticalSplitPane.add(_outputPane);
horizontalSplitPane.add(verticalSplitPane);
Container content = getContentPane();
content.add(_toolBar, BorderLayout.PAGE_START);
content.add(horizontalSplitPane, BorderLayout.CENTER);
content.add(_statusBar, BorderLayout.SOUTH);
_root.actionPerformed(null);
}
/**
* Gets the handle to OpenBooks service.
*/
public OpenBookService getService() {
if (_service == null) {
final String unitName = PropertyHelper.getString(_config, "openbook.unit",
OpenBookService.DEFAULT_UNIT_NAME);
SwingWorker<OpenBookService, Void> getService = new SwingWorker<OpenBookService, Void> () {
@Override
protected OpenBookService doInBackground() throws Exception {
return ServiceFactory.getService(unitName);
}
};
getService.execute();
try {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
_service = getService.get(10, TimeUnit.SECONDS);
} catch (Exception t) {
new ErrorDialog(t).setVisible(true);
} finally {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
return _service;
}
public Customer getCustomer() {
if (_customer == null) {
SwingWorker<Customer, Void> task = new SwingWorker<Customer, Void> () {
@Override
protected Customer doInBackground() throws Exception {
return getService().login("guest");
}
};
task.execute();
try {
_customer = task.get(1, TimeUnit.SECONDS);
} catch (Exception t) {
new ErrorDialog(t).setVisible(true);
}
}
return _customer;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
if (SwingUtilities.isEventDispatchThread()) {
new ErrorDialog(e);
} else {
e.printStackTrace();
}
}
private JToolBar createToolBar() {
JToolBar toolBar = new JToolBar();
toolBar.add(_buyBook);
toolBar.add(_deliver);
toolBar.add(_supply);
Dimension d = new Dimension(40, 32);
toolBar.addSeparator(d);
toolBar.add(_viewConfig);
toolBar.add(_viewDomain);
toolBar.add(_viewData);
toolBar.add(_viewSource);
toolBar.add(_viewQuery);
toolBar.addSeparator(d);
toolBar.add(Box.createHorizontalGlue());
toolBar.add(_about);
toolBar.add(Box.createHorizontalStrut(2));
return toolBar;
}
private StatusBar createStatusBar() {
return new StatusBar();
}
/**
* Abstract root of all Action objects helps to locate/configure visual action parameters such as
* tooltip text or image.
*
* @author Pinaki Poddar
*
*/
public abstract class OpenBookAction extends AbstractAction {
public OpenBookAction(Map<String,Object> props, String key) {
this(PropertyHelper.getString(props, key + "name", ""),
PropertyHelper.getString(props, key + "icon", null),
PropertyHelper.getString(props, key + "tooltip", ""),
PropertyHelper.getString(props, key + "help", ""));
}
public OpenBookAction(String name, String iconLocation, String tooltip) {
this(name, iconLocation, tooltip, tooltip);
}
public OpenBookAction(String name, String iconLocation, String tooltip, String helpText) {
putValue(Action.NAME, name);
putValue(Action.SHORT_DESCRIPTION, tooltip);
putValue(Action.LONG_DESCRIPTION, helpText);
Icon icon = Images.getIcon(iconLocation, true);
putValue(Action.SMALL_ICON, icon);
}
}
public class BuyBookAction extends OpenBookAction {
BuyBookPage _buyBookPage;
public BuyBookAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_buyBookPage == null) {
_buyBookPage = new BuyBookPage(getService(), getCustomer());
}
showTab(_tabbedPane, "Buy Books", _buyBookPage);
switchTab(_outputPane, _sqlLog);
}
}
public class DeliveryAction extends OpenBookAction {
DeliveryPage _deliveryPage;
public DeliveryAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_deliveryPage == null) {
_deliveryPage = new DeliveryPage(getService());
}
showTab(_tabbedPane, "Deliver Books", _deliveryPage);
switchTab(_outputPane, _sqlLog);
}
}
public class SupplyAction extends OpenBookAction {
SupplyPage _supplyPage;
public SupplyAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_supplyPage == null) {
_supplyPage = new SupplyPage(getService());
}
showTab(_tabbedPane, "Supply Books", _supplyPage);
switchTab(_outputPane, _sqlLog);
}
}
public class ViewConfigAction extends OpenBookAction {
ConfigurationViewer _configView;
public ViewConfigAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_configView == null) {
_configView = new ConfigurationViewer("Unit Configuration", getService().getUnit().getProperties());
showTab(_tabbedPane, "Configuration", new JScrollPane(_configView));
} else {
showTab(_tabbedPane, "Configuration", _configView);
}
}
}
public class ViewDomainAction extends OpenBookAction {
MetamodelView _domainView;
AttributeLegendView _legends;
public ViewDomainAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_domainView == null) {
_domainView = new MetamodelView(getService().getUnit().getMetamodel());
_legends = new AttributeLegendView();
showTab(_outputPane, "Legends", new JScrollPane(_legends));
}
showTab(_tabbedPane, "Domain Model", _domainView);
}
}
public class ViewDataAction extends OpenBookAction {
public ViewDataAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
showTab(_tabbedPane, "Buy Books", null);
}
}
public class ViewQueryCacheAction extends OpenBookAction {
PreparedQueryViewer _queryView;
public ViewQueryCacheAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_queryView == null) {
_queryView = new PreparedQueryViewer(OpenJPAPersistence.cast(getService().getUnit()));
showTab(_tabbedPane, "JPQL Query", new JScrollPane(_queryView));
}
showTab(_tabbedPane, "JPQL Queries", _queryView);
}
}
public class ViewSourceAction extends OpenBookAction {
SourceCodeViewer _sourceViewer;
public ViewSourceAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_sourceViewer == null) {
_sourceViewer = new SourceCodeViewer("source");
}
showTab(_tabbedPane, "Source Code", _sourceViewer);
}
}
/**
* Displays the "welcome" page.
*
* @author Pinaki Poddar
*
*/
public class WelcomeAction extends OpenBookAction {
PowerPointViewer _powerpoint;
JLabel _logoLabel = new JLabel(LOGO);
boolean _showPresentation = true;
public WelcomeAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_powerpoint == null && _showPresentation) {
String dir = PropertyHelper.getString(_config, "openbook.slides.dir", "slides/");
String[] defaultSlides = {
"Slide1.JPG",
"Slide2.JPG",
"Slide3.JPG",
"Slide4.JPG",
"Slide5.JPG",
"Slide6.JPG",
"Slide7.JPG",
"Slide8.JPG",
"Slide9.JPG",
"Slide10.JPG",
"Slide11.JPG",
"Slide12.JPG",
"Slide13.JPG",
"Slide14.JPG",
"Slide15.JPG"};
List<String> slides = PropertyHelper.getStringList(_config, "openbook.slides.list",
Arrays.asList(defaultSlides));
try {
_powerpoint = new PowerPointViewer(dir, slides);
} catch (Exception e1) {
_showPresentation = false;
System.err.println("Error while opening slide deck at " + dir + ". \r\n"+ e1);
}
}
showTab(_tabbedPane, "Home", _powerpoint != null ? _powerpoint : _logoLabel);
}
}
public class AboutAction extends OpenBookAction {
AboutDialog _dialog;
public AboutAction(String name, String iconLocation, String tooltip) {
super(name, iconLocation, tooltip);
}
public void actionPerformed(ActionEvent e) {
if (_dialog == null) {
_dialog = new AboutDialog(LOGO);
SwingHelper.position(_dialog, Demo.this);
}
_dialog.setVisible(true);
}
}
/**
* Show the given tab in the given pane.
* @param pane the tabbed pane
* @param title title of the tab component
* @param tab the component to show
*/
void showTab(JTabbedPane pane, String title, Component tab) {
if (tab == null)
return;
Component c = locateTab(pane, tab);
if (c == null) {
pane.addTab(title, tab);
pane.setSelectedComponent(tab);
} else {
pane.setSelectedComponent(c);
}
}
void switchTab(JTabbedPane pane, Component tab) {
if (tab == null)
return;
Component c = locateTab(pane, tab);
if (c == null) {
pane.setSelectedComponent(c);
}
}
Component locateTab(JTabbedPane pane, Component tab) {
int index = pane.indexOfComponent(tab);
if (index == -1) {
Component[] components = pane.getComponents();
for (int i = 0; i < components.length; i++) {
if (components[i] instanceof JScrollPane
&& (((JScrollPane)components[i]).getViewport().getView() == tab)) {
return components[i];
}
}
} else {
return pane.getComponentAt(index);
}
return null;
}
private JTabbedPane createTabbedView() {
JTabbedPane pane = new JTabbedPane();
pane.setPreferredSize(TAB_VIEW);
pane.setMinimumSize(new Dimension(TAB_VIEW.width, TAB_VIEW.height));
return pane;
}
private JTabbedPane createOutputView() {
JTabbedPane pane = new JTabbedPane();
pane.setPreferredSize(OUT_VIEW);
_sqlLog = new ScrollingTextPane();
GraphicOutputStream stream = new GraphicOutputStream(_sqlLog);
_sqlLog.setPreferredSize(TAB_VIEW);
SQLLogger.setOutput(stream);
pane.addTab("SQL Log", new JScrollPane(_sqlLog));
ScrollingTextPane consoleLog = new ScrollingTextPane();
GraphicOutputStream console = new GraphicOutputStream(consoleLog);
System.setErr(new PrintStream(console, true));
pane.addTab("Console", new JScrollPane(consoleLog));
return pane;
}
/**
* Creates the navigation tree and adds the tree nodes. Each tree node is attached with an action
* that fires when the node is selected.
*/
private JTree createNavigator() {
ActionTreeNode root = new ActionTreeNode(_root);
DefaultMutableTreeNode app = new DefaultMutableTreeNode("Application WorkFlows");
DefaultMutableTreeNode views = new DefaultMutableTreeNode("Views");
root.add(app);
root.add(views);
app.add(new ActionTreeNode(_buyBook));
app.add(new ActionTreeNode(_deliver));
app.add(new ActionTreeNode(_supply));
views.add(new ActionTreeNode(_viewConfig));
views.add(new ActionTreeNode(_viewDomain));
views.add(new ActionTreeNode(_viewQuery));
views.add(new ActionTreeNode(_viewData));
views.add(new ActionTreeNode(_viewSource));
JTree tree = new JTree(root);
tree.setShowsRootHandles(true);
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
Object treeNode = _navigator.getLastSelectedPathComponent();
if (treeNode instanceof ActionTreeNode) {
((ActionTreeNode)treeNode)._action.actionPerformed(null);
}
}
});
tree.setCellRenderer(new TypedTreeCellRenderer());
return tree;
}
/**
* A tree node which may have an associated action.
*
* @author Pinaki Poddar
*
*/
public static class ActionTreeNode extends DefaultMutableTreeNode {
private final Action _action;
public ActionTreeNode(Action action) {
_action = action;
}
public String toString() {
return _action.getValue(Action.SHORT_DESCRIPTION).toString();
}
}
public class TypedTreeCellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
}
}
public static class AboutDialog extends JDialog {
public AboutDialog(Icon logo) {
setModal(true);
setLayout(new BorderLayout());
JButton button = new JButton("<html>"
+ "<b>OpenBooks</b> "
+ "<br> using OpenJPA version " + OpenJPAVersion.MAJOR_RELEASE + "." + OpenJPAVersion.MINOR_RELEASE
+ "<br> by JPA Team, SWG"
+ "<br>IBM Corporation"
+ "<p>"
+ "</html>");
button.setIcon(logo);
button.setHorizontalTextPosition(SwingConstants.RIGHT);
button.setEnabled(true);
button.setBorderPainted(false);
add(button, BorderLayout.CENTER);
add(new JLabel(Images.getIcon("images/websphere.png")), BorderLayout.SOUTH);
setTitle("About OpenBooks");
setAlwaysOnTop(true);
setResizable(false);
pack();
}
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.Image;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.ImageIcon;
/**
* Utility to load and cache images.
*
* @author Pinaki Poddar
*
*/
public class Images {
private static Map<String, Icon> images = new HashMap<String, Icon>();
public static Icon NEXT = getIcon("images/nav_forward.gif");
public static Icon BACK = getIcon("images/nav_backward.gif");
public static Icon DONE = getIcon("images/done.png");
public static Icon CANCEL = getIcon("images/cancel.png");
public static Icon ERROR = getIcon("images/error.png");
public static Icon BROWSE = getIcon("images/browse.png");
public static Icon START = getIcon("images/start_task.gif");
public static Icon MONITOR = getIcon("images/console_view.gif");
public static Icon getIcon(String name) {
Icon icon = images.get(name);
if (icon == null) {
icon = createImageIcon(name);
images.put(name, icon);
}
return icon;
}
public static Icon getIcon(String name, boolean scale) {
return getIcon(name, 32, -1);
}
public static Icon getIcon(String name, int width, int height) {
Icon icon = getIcon(name);
if (icon == null) {
return null;
}
icon = new ImageIcon(((ImageIcon)icon).getImage().getScaledInstance(32, -1, Image.SCALE_SMOOTH));
return icon;
}
/**
* Returns an ImageIcon, or null if the path was invalid.
*
**/
protected static ImageIcon createImageIcon(String path) {
if (path == null)
return null;
URL imgURL = Thread.currentThread().getContextClassLoader().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
imgURL = Images.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.Color;
import java.io.PrintStream;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import jpa.tools.swing.GraphicOutputStream;
import org.apache.openjpa.lib.jdbc.AbstractJDBCListener;
import org.apache.openjpa.lib.jdbc.JDBCEvent;
/**
* Logs SQL statement to a graphic console.
*
* @author Pinaki Poddar
*
*/
public class SQLLogger extends AbstractJDBCListener {
private static PrintStream out = null;
private static AttributeSet red, green, blue, magenta;
static {
StyleContext ctx = StyleContext.getDefaultStyleContext();
red = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED);
green = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.GREEN);
blue = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.BLUE);
magenta = ctx.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.MAGENTA);
}
public static void setOutput(GraphicOutputStream o) {
out = new PrintStream(o, true);
o.registerStyle("INSERT", green);
o.registerStyle("SELECT", blue);
o.registerStyle("UPDATE", magenta);
o.registerStyle("DELETE", red);
}
@Override
public void beforeExecuteStatement(final JDBCEvent event) {
if (out == null)
return;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
out.println(event.getSQL());
}
});
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.client;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import jpa.tools.swing.EntityDataModel;
import jpa.tools.swing.EntityTableView;
import jpa.tools.swing.ErrorDialog;
import openbook.domain.Book;
import openbook.domain.Inventory;
import openbook.server.OpenBookService;
/**
* A page to view and supply low inventory items.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class SupplyPage extends JPanel {
private final OpenBookService _service;
private final EntityTableView<Inventory> _lowInventories;
private final JButton _supply;
private final JButton _view;
private final JLabel _title;
private static int REORDER_LIMIT = 10;
private static int REORDER_QUANTITY = 40;
public SupplyPage(final OpenBookService service) {
setLayout(new BorderLayout());
_service = service;
_title = new JLabel(REORDER_LIMIT + " lowest inventory items");
_view = new JButton("Show " + REORDER_LIMIT + " lowest inventory items");
_supply = new JButton("Supply " + REORDER_QUANTITY + " to each item");
List<Inventory> orders = getInventory(REORDER_LIMIT);
_lowInventories = new EntityTableView<Inventory>(Inventory.class,
orders,
EntityDataModel.BASIC_ATTR | EntityDataModel.ASSOCIATION_ATTR,
service.getUnit());
add(_title, BorderLayout.NORTH);
add(_lowInventories, BorderLayout.CENTER);
JPanel buttons = new JPanel();
buttons.add(_view);
buttons.add(_supply);
add(buttons, BorderLayout.SOUTH);
_view.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
_lowInventories.getDataModel().updateData(getInventory(REORDER_LIMIT));
}
});
/**
* Supplies each inventory displayed.
*/
_supply.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new SwingWorker<List<Inventory>, Void>() {
@Override
protected List<Inventory> doInBackground() throws Exception {
EntityDataModel<Inventory> invs = _lowInventories.getDataModel();
List<Inventory> updated = new ArrayList<Inventory>();
for (Inventory inv : invs) {
Book supplied = _service.supply(inv.getBook(), REORDER_QUANTITY);
updated.add(supplied.getInventory());
}
return updated;
}
public void done() {
try {
_lowInventories.getDataModel().updateData(get(1, TimeUnit.SECONDS));
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
}
}
}.execute();
}
});
}
/**
* Gets the orders in a background (i.e. not AWT event dispatch thread) thread.
* <br>
* But blocks painting anyway, because that is what is intended.
*
*/
private List<Inventory> getInventory(final Integer limit) {
SwingWorker<List<Inventory>, Void> worker = new SwingWorker<List<Inventory>, Void>() {
@Override
protected List<Inventory> doInBackground() throws Exception {
return _service.getReorderableBooks(REORDER_LIMIT);
}
};
worker.execute();
try {
return worker.get();
} catch (Exception e) {
new ErrorDialog(e).setVisible(true);
}
return Collections.emptyList();
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Version;
/**
* A persistent entity to represent an author of one or more Book.
* <br>
* <b>Notes</b>: No setter for identity value.
* <br>
* <LI><b>Identity</b>:Generated value as identity.
* <LI><b>Mapping</b>:Many-to-Many mapping to Books.
* <LI><b>Version</b>: Yes.
*
* @author Pinaki Poddar
*
*/
@Entity
public class Author {
@Id
@GeneratedValue
private long id;
private String name;
@ManyToMany(mappedBy="authors")
private Set<Book> books;
@Version
private int version;
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void addBook(Book book) {
if (books == null)
books = new HashSet<Book>();
books.add(book);
}
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,19 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.Author.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class Author_ {
public static volatile SetAttribute<Author,Book> books;
public static volatile SingularAttribute<Author,Long> id;
public static volatile SingularAttribute<Author,String> name;
public static volatile SingularAttribute<Author,Integer> version;
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
import javax.persistence.Version;
/**
* An immutable persistent entity represents a Book.
* <br>
* The mutable properties of the book such as number of items in stock etc.
* are factored out in a separate {@link Inventory} instance.
* <br>
* The state of inventory is mutable, but the relation to inventory is immutable.
*
* <LI><b>Identity</b>: Application-defined identity.
* <LI><b>Mapping</b>: One-to-One bi-directional, immutable mapping to {@link Inventory}.
* Many-to-Many bi-directional mapping to {@linkplain Author}.
* <LI><b>Version</b>: No.
* <p>
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Book implements Serializable {
@Id
private String ISBN;
private String title;
private double price;
@OneToOne(mappedBy="book",
fetch=FetchType.LAZY,
cascade=CascadeType.ALL,
optional=false,
orphanRemoval=true)
private Inventory inventory;
@ManyToMany(fetch=FetchType.EAGER)
private List<Author> authors;
/**
* A no-arg constructor is required for JPA Specification.
*/
public Book() {
}
/**
* Construct a book with given parameters.
*
* @param ISBN primary identity of this Book
* @param title Title of the book.
* @param price price of the book.
* @param initialSupply initial inventory quantity.
*/
public Book(String ISBN, String title, double price, int initialSupply) {
this.ISBN = ISBN;
this.title = title;
this.price = price;
inventory = new Inventory(this, initialSupply);
}
public String getISBN() {
return ISBN;
}
public String getTitle() {
return title;
}
public double getPrice() {
return price;
}
public List<Author> getAuthors() {
return authors;
}
public void addAuthor(Author...authors) {
if (this.authors == null)
this.authors = new ArrayList<Author>();
for (Author a : authors) {
if (!this.authors.contains(a))
this.authors.add(a);
}
}
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
public Inventory getInventory() {
return inventory;
}
@Version
private int version;
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,21 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.Book.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class Book_ {
public static volatile SingularAttribute<Book,String> ISBN;
public static volatile ListAttribute<Book,Author> authors;
public static volatile SingularAttribute<Book,Inventory> inventory;
public static volatile SingularAttribute<Book,Double> price;
public static volatile SingularAttribute<Book,String> title;
public static volatile SingularAttribute<Book,Integer> version;
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
/**
* A persistent entity represents a Customer.
*
* <br><b>Persistent Identity</b>: auto-generated identity.
* <br><b>Mapping</b>:
*
* <br><b>Design Notes</b>: No setter for identity value.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Customer implements Serializable {
@Id
@GeneratedValue
private long id;
private String name;
private String email;
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
/**
* Create a {@link ShoppingCart} for this customer.
* @return
*/
public ShoppingCart newCart() {
return new ShoppingCart(this);
}
@Version
private int version;
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,18 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.Customer.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class Customer_ {
public static volatile SingularAttribute<Customer,String> email;
public static volatile SingularAttribute<Customer,Long> id;
public static volatile SingularAttribute<Customer,String> name;
public static volatile SingularAttribute<Customer,Integer> version;
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Version;
/**
* A mutable persistent entity.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Inventory implements Serializable {
@Id
@OneToOne(fetch=FetchType.EAGER, optional=false)
private Book book;
private int supplied;
private int sold;
protected Inventory() {
}
/**
* Construct with the given Book and initial inventory count.
* Package protected because only a Book can create its own inventory.
*
* @param book non-null Book.
* @param initialSupply must be greater than zero.
*/
Inventory(Book book, int initialSupply) {
if (book == null)
throw new NullPointerException("Can not create inventory for null Book");
if (initialSupply < 1)
throw new IllegalArgumentException("Can not create inventory " + initialSupply + " for " + book +
" Initial inventory must be greater than zero.");
this.book = book;
increment(initialSupply);
}
/**
* Gets the Book that this inventory represents.
*
* @return non-null Book.
*/
public Book getBook() {
return book;
}
/**
* Gets the available quantity.
* This is an <em>in-flight</em> value representing the difference between the quantity supplied and quantity sold
* so far.
*/
public int getInStock() {
return supplied - sold;
}
/**
* Gets the quantity supplied so far.
*
* @return a monotonically increasing positive number.
*/
public int getSupplied() {
return supplied;
}
/**
* Gets the quantity sold so far.
*
* @return a monotonically increasing positive number.
*/
public int getSold() {
return sold;
}
/**
* Increment this inventory by the given quantity.
*
* @param supplied must be positive.
*/
public void increment(int supplied) {
if (supplied < 1)
throw new IllegalArgumentException("Can not add " + supplied + " supplies to " + this +
" Suuplied quanlity must be greater than zero.");
this.supplied += supplied;
}
/**
* Decrement this inventory by the given quantity.
*
* @param sold must be positive.
*/
public void decrement(int sold) {
if (sold < 1)
throw new IllegalArgumentException("can not sell " + sold + "quantity to " + this
+ "Sold quantity Must be greater than zero.");
this.sold += sold;
}
@Version
private int version;
public int getVersion() {
return version;
}
}

View File

@ -0,0 +1,18 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.Inventory.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class Inventory_ {
public static volatile SingularAttribute<Inventory,Book> book;
public static volatile SingularAttribute<Inventory,Integer> sold;
public static volatile SingularAttribute<Inventory,Integer> supplied;
public static volatile SingularAttribute<Inventory,Integer> version;
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.OrderColumn;
/**
* An immutable persistent entity with complex primary key.
* The primary key is combination of the primary identity of {@linkplain PurchaseOrder} and
* an 1-based integer index.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
@IdClass(LineItem.LineItemId.class)
public class LineItem implements Serializable {
@Id
@OneToOne
private PurchaseOrder order;
@Id
@OrderColumn
@Column(name="IDX") // index is keyword
private int index;
@ManyToOne(optional=false)
private Book book;
private int quantity;
protected LineItem() {
}
/**
* Constructed as a line item for the given PurchaseOrder for the given Book for the given quantity.
* Package protected because only the PurchaseOrder can create its own LineItem.
*
* @param order non-null PurchaseOrder
* @param i the 1-based index of this line item in its parent PurchaseOrder
* @param book non-null Book of this line item
* @param quantity no. of books must be greater than zero.
*/
LineItem(PurchaseOrder order, int i, Book book, int quantity) {
if (order == null)
throw new NullPointerException("Can not create LineItem for null PurchaseOrder");
if (i < 1)
throw new IllegalArgumentException("Can not create LineItem with index " + i + ". Must be > 0");
if (book == null)
throw new NullPointerException("Can not create LineItem for null Book");
if (quantity < 1)
throw new IllegalArgumentException("Can not create LineItem with quantity " + i + ". Must be > 0");
this.order = order;
this.index = i;
this.book = book;
this.quantity = quantity;
}
/**
* Gets the Book for this line item.
* @return non-null Book.
*/
public Book getBook() {
return book;
}
/**
* Gets the quantity of the book for this line item.
* @return a positive number.
*/
public int getQuantity() {
return quantity;
}
/**
* Gets the parent PurchaseOrder of this line item.
* @return non-null PurchaseOrder.
*/
public PurchaseOrder getOrder() {
return order;
}
/**
* Gets the 1-based index this line item in its parent PurchaseOrder.
* @return index must be greater than zero.
*/
public int getIndex() {
return index;
}
/**
* Separate identity class.
*
* @author Pinaki Poddar
*
*/
public static class LineItemId implements Serializable {
long order;
int index;
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if (other instanceof LineItemId) {
LineItemId that = (LineItemId)other;
return order == that.order && index == that.index;
}
return false;
}
@Override
public int hashCode() {
return (int) (31 ^ order + index);
}
}
}

View File

@ -0,0 +1,18 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.LineItem.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class LineItem_ {
public static volatile SingularAttribute<LineItem,Book> book;
public static volatile SingularAttribute<LineItem,Integer> index;
public static volatile SingularAttribute<LineItem,PurchaseOrder> order;
public static volatile SingularAttribute<LineItem,Integer> quantity;
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
* A persistent entity.
* Auto-generated identity.
* Enum and Date type persistent attribute.
* One-to-One uni-directional mapping to Customer.
* One-to-Many bi-directional mapping to LineItem.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class PurchaseOrder implements Serializable {
public enum Status {PENDING, DELEVERED};
@Id
@GeneratedValue
private long id;
@OneToOne(optional=false)
private Customer customer;
private Status status;
@OneToMany(mappedBy="order", cascade=CascadeType.ALL, orphanRemoval=true)
private List<LineItem> items;
private int total;
@Temporal(TemporalType.TIME)
private Date placedOn;
@Temporal(TemporalType.TIME)
private Date deliveredOn;
protected PurchaseOrder() {}
/**
* Constructed by transferring the content of the given {@linkplain ShoppingCart}.
* @param cart
*/
public PurchaseOrder(ShoppingCart cart) {
customer = cart.getCustomer();
status = Status.PENDING;
placedOn = new Time(System.currentTimeMillis());
Map<Book, Integer> items = cart.getItems();
for (Map.Entry<Book, Integer> entry : items.entrySet()) {
addItem(entry.getKey(), entry.getValue());
}
}
public long getId() {
return id;
}
public Customer getCustomer() {
return customer;
}
public Status getStatus() {
return status;
}
public void setDelivered() {
if (this.status == Status.DELEVERED)
throw new IllegalStateException(this + " has been delivered");
this.status = Status.DELEVERED;
this.deliveredOn = new Time(System.currentTimeMillis());
}
public List<LineItem> getItems() {
return items;
}
void addItem(Book book, int quantity) {
if (items == null)
items = new ArrayList<LineItem>();
items.add(new LineItem(this, items.size()+1, book, quantity));
total += (book.getPrice() * quantity);
}
public double getTotal() {
return total;
}
public Date getPlacedOn() {
return placedOn;
}
public Date getDeliveredOn() {
return deliveredOn;
}
}

View File

@ -0,0 +1,24 @@
/**
* Generated by OpenJPA MetaModel Generator Tool.
**/
package openbook.domain;
import java.util.Date;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
import openbook.domain.PurchaseOrder.Status;
@javax.persistence.metamodel.StaticMetamodel
(value=openbook.domain.PurchaseOrder.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Thu May 13 20:18:23 CDT 2010")
public class PurchaseOrder_ {
public static volatile SingularAttribute<PurchaseOrder,Customer> customer;
public static volatile SingularAttribute<PurchaseOrder,Date> deliveredOn;
public static volatile SingularAttribute<PurchaseOrder,Long> id;
public static volatile ListAttribute<PurchaseOrder,LineItem> items;
public static volatile SingularAttribute<PurchaseOrder,Date> placedOn;
public static volatile SingularAttribute<PurchaseOrder,Status> status;
public static volatile SingularAttribute<PurchaseOrder,Integer> total;
}

View File

@ -0,0 +1,151 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
/**
* A simple numeric range.
* Minimum value is included, maximum value is excluded.
*
* @author Pinaki Poddar
*
*/
public class Range<N extends Number> {
private N min;
private N max;
private final Class<N> type;
public Range(Object min, Object max) {
this((N)min, (N)max);
}
/**
* Creates a range. Empty range i.e. where minimum equals maximum is allowed.
*
* @param min non-null minimum value.
* @param max non-null maximum value.
*/
public Range(N min, N max) {
if (min == null || max == null)
throw new IllegalArgumentException("Supplied Min or Max is null");
if (max.doubleValue() < min.doubleValue())
throw new IllegalArgumentException("Invalid range (" + min + "," + max + ")");
this.min = min;
this.max = max;
type = (Class<N>)min.getClass();
}
public Class<N> type() {
return type;
}
/**
* Affirms if the given value is within this range.
* Minimum is included, maximum is excluded.
*
* @param x a non-null value
* @return true if the given value is greater than or equals to minimum and less than the maximum.
*/
public boolean contains(Number x) {
return x != null && x.doubleValue() >= min.doubleValue() && x.doubleValue() < max.doubleValue();
}
/**
* Affirms if the given range is within this range.
* Minimum is included, maximum is excluded.
*
* @param x a non-null value
* @return true if the given value is greater than or equals to minimum and less than the maximum.
*/
public <X extends Number> boolean contains(Range<X> r) {
return r != null && r.getMinimum().doubleValue() >= min.doubleValue() &&
r.getMaximum().doubleValue() <= max.doubleValue();
}
/**
* Gets the minimum value.
*/
public N getMinimum() {
return min;
}
/**
* Gets the maximum value.
*/
public N getMaximum() {
return max;
}
/**
* Adjusts this range by the given number.
*
* @param x a non-null value.
*
* @return if this range is adjusted by this value.
*/
public boolean adjust(N x) {
if (x == null)
return false;
if (x.doubleValue() < min.doubleValue()) {
min = x;
return true;
}
if (x.doubleValue() > max.doubleValue()) {
max = x;
return true;
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((max == null) ? 0 : max.hashCode());
result = prime * result + ((min == null) ? 0 : min.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Range other = (Range) obj;
if (max == null) {
if (other.max != null)
return false;
} else if (!max.equals(other.max))
return false;
if (min == null) {
if (other.min != null)
return false;
} else if (!min.equals(other.min))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.domain;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* A non-persistent entity holds the content of a shopping session for a {@linkplain Customer}.
* Used to create a persistent PurchaseOrder.
* Books can be added or removed from this cart.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class ShoppingCart implements Serializable {
private Customer customer;
private Map<Book, Integer> items;
/**
* Construct a cart for the given Customer.
*
* @param c non-null Customer.
*/
ShoppingCart(Customer c) {
customer = c;
items = new HashMap<Book, Integer>();
}
/**
* Gets the Customer who owns this cart.
*
* @return non-null Customer.
*/
public Customer getCustomer() {
return customer;
}
/**
* Gets the books with their corresponding quantity in this cart.
*
* @return
*/
public Map<Book, Integer> getItems() {
return items;
}
/**
* Add the given book with the given quantity in the cart.
* If the book already exists then the quantity is added to the existing quantity.
*
* @param book non-null Book
* @param quantity a positive quantity.
*/
public void addItem(Book book, int quantity) {
if (book == null)
throw new NullPointerException("Can not add null Book to " + this);
if (quantity < 1)
throw new IllegalArgumentException("Can not add " + quantity + " " + book + " to " + this +
" Added qunatity must be greater than zero");
int current = items.containsKey(book) ? items.get(book) : 0;
items.put(book, current + quantity);
}
/**
* Change the quantity of the given book by the given delta.
*
* @param book a non-null Book that must exist in this cart.
* @param delta no. of quantity to change. Can be positive or negative.
* If the resultant quantity becomes zero of negative, the book is removed from the cart.
*/
public void changeQuantity(Book book, int delta) {
if (book == null)
throw new NullPointerException("Can not change quantity for null Book in " + this);
if (!items.containsKey(book))
throw new IllegalArgumentException("Can not change quantity for " + book + " becuase the book does not " +
"exist in " + this);
int current = items.containsKey(book) ? items.get(book) : 0;
if (current + delta <= 0) {
items.remove(book);
} else {
items.put(book, current + delta);
}
}
/**
* Removes the given book from this cart.
*
* @param book book a non-null Book that must exist in this cart.
*/
public void remove(Book book) {
if (book == null)
throw new NullPointerException("Can not remove null Book from " + this);
if (!items.containsKey(book))
throw new IllegalArgumentException("Can not remove " + book + " becuase the book does not " +
"exist in " + this);
items.remove(book);
}
public void clear() {
items.clear();
}
}

View File

@ -0,0 +1,181 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import openbook.domain.Book;
import openbook.domain.Customer;
import openbook.domain.Inventory;
import openbook.domain.PurchaseOrder;
import openbook.domain.ShoppingCart;
/**
* A simple service to select Books, purchase them and manage their inventory.
* A service handle can be obtained from {@link ServiceFactory#getService(String)
* Service Factory}.
*
* @author Pinaki Poddar
*
*/
public interface OpenBookService {
public static final String DEFAULT_UNIT_NAME = "OpenBooks";
/**
* Starts a session for the given named Customer.
* If no record of the given name exists, a new Customer record is created.
*
* @param name name of a possibly existing customer. Or a new one.
*
* @return a Customer
*/
public Customer login(String name);
/**
* Selects a list of Books matching the given conditions.
* Each of the conditional parameter can be null.
* A null parameter implies that the resultant query should ignore that parameter.
*
* @param title title of the Book
* @param min minimum price
* @param max maximum price
* @param author name of author
* @param decorators to modify the executable query such as its range.
* @return
*/
public List<Book> select(String title,
Double min, Double max,
String author,
QueryDecorator...decorators);
/**
* Gets the query String for the given parameters.
* Each of the conditional parameter can be null.
* A null parameter implies that the resultant query should ignore that parameter.
*
* @param title title of the Book
* @param min minimum price
* @param max maximum price
* @param author name of author
* @param decorators to modify the executable query such as its range.
* @return
*/
public String getQuery(String title,
Double min, Double max,
String author);
/**
* Runs an arbitrary JPQL query to return a list of result.
*
* @param jpql a valid JPQL query string.
* @param resultClass type of the result.
* @param decorators zero or more QueryDecorators to be applied before Query is executed.
* @return the selected instances.
*/
<T> List<T> query(String jpql, Class<T> resultClass, QueryDecorator...decorators);
<T> List<T> getExtent(Class<T> entity);
/**
* Buys the content of the given cart.
*
* @param cart a non-empty cart.
* @return a PurchaseOrder for the content of the cart.
*/
public PurchaseOrder placeOrder(ShoppingCart cart);
/**
* Delivers the given order. Delivery changes the status of the order, decrements
* inventory and finally removes the line items.
*
* @param order a PENDING order to be delivered.
* @return the PurchaseOrder after delivery.
*
*/
public PurchaseOrder deliver(PurchaseOrder order);
/**
* Add inventory of the given Book by the given quantity.
*
* @param b a Book whose inventory is to be incremented
* @param quantity positive number.
*
* @return the Book after incrementing its inventory.
*/
public Book supply(Book b, int quantity);
/**
* Gets the list of orders of given status.
*
* @param status status of the orders. null implies all orders.
*
* @return list of orders sorted by their placement dates.
*/
public List<PurchaseOrder> getOrders(PurchaseOrder.Status status);
/**
* Gets the list of Books whose inventory is lower than the given limit.
*
* @param limit reorder limit. null implies all inventory.
*
* @return list of Books with inventory lower than the given limit.
*/
public List<Inventory> getReorderableBooks(int limit);
/**
* Count the number of instances of the given persistent type.
*
* @param cls a persistent type.
* @return number of persistent entity of the given type.
*/
public long count(Class<?> cls);
/**
* Populates the underlying data repository with sample values, only if
* the data repository is empty.
*
* @param loadParameters control the number of Books etc. to be created.
* null is allowed.
*
* @return true if the repository is initialized by this invocation.
*/
public boolean initialize(Map<String,Object> loadParameters);
public void clean();
/**
* Gets the underlying persistence unit.
*
* @return
*/
public EntityManagerFactory getUnit();
/**
* Gets the name of the underlying persistence unit.
*
* @return
*/
public String getUnitName();
/**
* Affirms if the transaction on this persistence unit is managed by a container.
*
* @return
*/
public boolean isManaged();
}

View File

@ -0,0 +1,402 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;
import openbook.domain.Author;
import openbook.domain.Author_;
import openbook.domain.Book;
import openbook.domain.Book_;
import openbook.domain.Customer;
import openbook.domain.Customer_;
import openbook.domain.Inventory;
import openbook.domain.Inventory_;
import openbook.domain.LineItem;
import openbook.domain.PurchaseOrder;
import openbook.domain.PurchaseOrder_;
import openbook.domain.Range;
import openbook.domain.ShoppingCart;
import openbook.util.PropertyHelper;
import openbook.util.Randomizer;
import org.apache.openjpa.persistence.criteria.OpenJPACriteriaBuilder;
/**
* A demonstrative example of a transaction service with persistent entity using Java Persistence API.
* <br>
* This example service operates on a persistent domain model to browse {@linkplain Book books},
* occasionally {@linkplain #placeOrder(ShoppingCart) placing} {@linkplain PurchaseOrder purchase orders},
* while {@linkplain Inventory inventory} gets updated either by {@linkplain #deliver() delivery} or
* by {@linkplain #supply() supply}.
* <br>
* The operational model as well as the persistent domain model is influenced by the fact that
* a JPA based application can benefit from
* <LI>Mostly Immutable Persistent Data Model
* <LI>Optimistic Transaction Model
* <br>for better scalability and throughput.
* <br>
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
class OpenBookServiceImpl extends PersistenceService implements OpenBookService {
public static final int CUSTOMER_COUNT = 10;
public static final int BOOK_COUNT = 100;
public static final int AUTHOR_COUNT = 40;
public static final int AUTHOR_PER_BOOK = 3;
/**
* Range of number of queries executed for a {@linkplain #shop() shopping} trip.
*/
public static final Range<Double> PRICE_RANGE = new Range<Double>(4.99, 120.99);
public static final Range<Integer> STOCK_RANGE = new Range<Integer>(5, 50);
public static final int REORDER_LEVEL = 10;
OpenBookServiceImpl(String unit, EntityManagerFactory emf, boolean managed,
PersistenceContextType scope) {
super(unit, emf, managed, scope);
}
/**
* Initialize service by populating inventory of Books and Customers.
* If the inventory exists, then returns immediately without creating any new inventory.
*
* @return true if new inventory is created. false otherwise.
*/
public boolean initialize(Map<String,Object> config) {
if (isInitialized()) {
return false;
}
EntityManager em = begin();
if (config == null) {
config = Collections.EMPTY_MAP;
}
int nCustomer = PropertyHelper.getInteger(config, "openbook.Customer.Count", CUSTOMER_COUNT);
int nBook = PropertyHelper.getInteger(config, "openbook.Book.Count", BOOK_COUNT);
int nAuthor = PropertyHelper.getInteger(config, "openbook.Author.Count", AUTHOR_COUNT);
int nAuthorPerBook = PropertyHelper.getInteger(config, "openbook.Book.Author.Count", AUTHOR_PER_BOOK);
Double priceMax = PropertyHelper.getDouble(config, "openbook.Book.Price.Max", PRICE_RANGE.getMaximum());
Double priceMin = PropertyHelper.getDouble(config, "openbook.Book.Price.Min", PRICE_RANGE.getMinimum());
Integer stockMax = PropertyHelper.getInteger(config, "openbook.Inventory.Max", STOCK_RANGE.getMaximum());
Integer stockMin = PropertyHelper.getInteger(config, "openbook.Inventory.Min", STOCK_RANGE.getMinimum());
System.err.println("Creating " + nCustomer + " new Customer");
for (int i = 1; i < nCustomer; i++) {
Customer customer = new Customer();
customer.setName("Customer-"+i);
em.persist(customer);
}
List<Author> allAuthors = new ArrayList<Author>();
System.err.println("Creating " + nAuthor + " new Authors");
for (int i = 1; i <= nAuthor; i++) {
Author author = new Author();
author.setName("Author-"+i);
allAuthors.add(author);
em.persist(author);
}
System.err.println("Creating " + nBook + " new Books");
System.err.println("Linking at most " + nAuthorPerBook + " Authors per Book");
for (int i = 1; i <= nBook; i++) {
Book book = new Book(Randomizer.randomString(4,2),
"Book-" + i,
Randomizer.random(priceMin, priceMax),
Randomizer.random(stockMin, stockMax));
List<Author> authors = Randomizer.selectRandom(allAuthors,
Math.max(1, Randomizer.random(nAuthorPerBook)));
for (Author author : authors) {
author.addBook(book);
book.addAuthor(author);
}
em.persist(book);
}
commit();
return true;
}
/**
* Affirms whether the database is loaded with some records.
*/
public boolean isInitialized() {
return count(Book.class) > 0;
}
/**
* Provide a name and email to login a Customer.
* If such a customer exists, return it. Otherwise creates a new one.
* @param name
* @return
*/
public Customer login(String name) {
EntityManager em = begin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> q = cb.createQuery(Customer.class);
Customer customer = null;
Root<Customer> root = q.from(Customer.class);
ParameterExpression<String> pName = cb.parameter(String.class);
q.where(cb.equal(root.get(Customer_.name), pName));
List<Customer> customers = em.createQuery(q)
.setParameter(pName, name)
.getResultList();
if (customers.isEmpty()) {
Customer newCustomer = new Customer();
newCustomer.setName(name);
em.persist(newCustomer);
customer = newCustomer;
} else {
customer = customers.get(0);
}
commit();
return customer;
}
/**
* Find books that match title and price range.
* @param title
* @param minPrice
* @param maxPrice
* @param author
* @return
*/
public List<Book> select(String title, Double minPrice, Double maxPrice, String author,
QueryDecorator...decorators) {
CriteriaQuery<Book> q = buildQuery(title, minPrice, maxPrice, author);
EntityManager em = begin();
TypedQuery<Book> query = em.createQuery(q);
List<Book> result = query.getResultList();
commit();
return result;
}
public String getQuery(String title, Double minPrice, Double maxPrice, String author) {
CriteriaQuery<Book> q = buildQuery(title, minPrice, maxPrice, author);
return q.toString();
}
/**
* Creates a Query based on the values of the user input form.
* The user may or may not have filled a value for each form field
* and accordingly the query will be different.<br>
* This is typical of a form-based query. To account for all possible
* combinations of field values to build a String-based JPQL can be
* a daunting exercise. This method demonstrates how such dynamic,
* conditional be alternatively developed using {@link CriteriaQuery}
* introduced in JPA version 2.0.
* <br>
*
*
* @return a typed query
*/
private CriteriaQuery<Book> buildQuery(String title,
Double minPrice, Double maxPrice,
String author) {
CriteriaBuilder cb = getUnit().getCriteriaBuilder();
CriteriaQuery<Book> q = cb.createQuery(Book.class);
Root<Book> book = q.from(Book.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (title != null && title.trim().length() > 0) {
Predicate matchTitle = cb.like(book.get(Book_.title), title);
predicates.add(matchTitle);
}
if (minPrice != null && maxPrice != null) {
Predicate matchPrice = cb.between(book.get(Book_.price), minPrice, maxPrice);
predicates.add(matchPrice);
} else if (minPrice != null && maxPrice == null) {
Predicate matchPrice = cb.ge(book.get(Book_.price), minPrice);
predicates.add(matchPrice);
} else if (minPrice == null && maxPrice != null) {
Predicate matchPrice = cb.le(book.get(Book_.price), maxPrice);
predicates.add(matchPrice);
}
if (author != null && author.trim().length() > 0) {
Predicate matchAuthor = cb.like(book.join(Book_.authors).get(Author_.name), "%"+author+"%");
predicates.add(matchAuthor);
}
q.select(book);
if (!predicates.isEmpty())
q.where(predicates.toArray(new Predicate[predicates.size()]));
return q;
}
/**
* Deliver pending orders.
* Queries for pending PurchaseOrders and attempts to deliver each in a separate
* transaction. Some of the transactions may fail because of concurrent modification
* on the inventory by the supplier.
*/
public PurchaseOrder deliver(PurchaseOrder o) {
EntityManager em = begin();
o = em.merge(o);
o.setDelivered();
for (LineItem item : o.getItems()) {
item.getBook().getInventory().decrement(item.getQuantity());
}
commit();
return o;
}
public List<PurchaseOrder> getOrders(PurchaseOrder.Status status) {
EntityManager em = begin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<PurchaseOrder> q = cb.createQuery(PurchaseOrder.class);
Root<PurchaseOrder> order = q.from(PurchaseOrder.class);
q.select(order);
if (status != null) {
q.where(cb.equal(order.get(PurchaseOrder_.status), status));
}
q.orderBy(cb.desc(order.get(PurchaseOrder_.placedOn)));
TypedQuery<PurchaseOrder> query = em.createQuery(q);
List<PurchaseOrder> result = query.getResultList();
commit();
return result;
}
/**
* Creates a new {@linkplain PurchaseOrder} from the content of the given {@linkplain ShoppingCart}.
* <br>
* The transaction is not expected to fail because the inventory is
* not modified by placing an order.
*
* @param cart a non-null Shopping cart.
*/
public PurchaseOrder placeOrder(ShoppingCart cart) {
EntityManager em = begin();
PurchaseOrder order = new PurchaseOrder(cart);
em.persist(order);
commit();
return order;
}
/**
* Supply books that have low inventory.
* <br>
* Queries for books with low inventory and supply each book in separate
* transaction. Some of the transactions may fail due to concurrent modification on
* the {@linkplain Inventory} by the {@linkplain #deliver() delivery} process.
*/
public Book supply(Book b, int quantity) {
EntityManager em = begin();
b = em.merge(b);
b.getInventory().increment(quantity);
commit();
return b;
}
public List<Inventory> getReorderableBooks(int limit) {
EntityManager em = begin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Inventory> q = cb.createQuery(Inventory.class);
Root<Inventory> inv = q.from(Inventory.class);
q.select(inv);
Expression<Integer> inStock = cb.diff(
inv.get(Inventory_.supplied),
inv.get(Inventory_.sold));
q.orderBy(cb.asc(inStock));
List<Inventory> result = em.createQuery(q)
.setMaxResults(limit)
.getResultList();
commit();
return result;
}
public long count(Class<?> cls) {
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> c = cb.createQuery(Long.class);
Root<?> from = c.from(cls);
c.select(cb.count(from));
return em.createQuery(c).getSingleResult();
}
public List<Book> selectByExample(Book b, QueryDecorator...decorators) {
return queryByTemplate(Book.class, b);
}
private <T> List<T> queryByTemplate(Class<T> type, T template) {
EntityManager em = begin();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> c = cb.createQuery(type);
c.where(((OpenJPACriteriaBuilder)cb).qbe(c.from(type), template));
List<T> result = em.createQuery(c).getResultList();
commit();
return result;
}
public <T> List<T> getExtent(Class<T> entityClass) {
EntityManager em = begin();
CriteriaQuery<T> c = em.getCriteriaBuilder().createQuery(entityClass);
c.from(entityClass);
List<T> result = em.createQuery(c).getResultList();
commit();
return result;
}
public <T> List<T> query(String jpql, Class<T> resultClass, QueryDecorator... decorators) {
EntityManager em = begin();
TypedQuery<T> query = em.createQuery(jpql, resultClass);
if (decorators != null) {
for (QueryDecorator decorator : decorators) {
decorator.decorate(query);
}
}
List<T> result = query.getResultList();
commit();
return result;
}
public void clean() {
EntityManager em = begin();
Set<EntityType<?>> entities = em.getMetamodel().getEntities();
for (EntityType<?> type : entities) {
System.err.println("Deleting all instances of " + type.getName());
em.createQuery("delete from " + type.getName() + " p").executeUpdate();
}
commit();
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import java.io.Serializable;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContextType;
/**
* An abstract utility for JPA based service.
* This thin wrapper over a {@link EntityManagerFactory Persistence Unit} maintains
* <LI>per-thread persistence context
* <LI>relinquishes direct transaction control under a managed environment
*
* @see #getEntityManager()
* @see #newEntityManager()
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
abstract class PersistenceService implements Serializable {
private final EntityManagerFactory emf;
private final String unitName;
private final boolean isManaged;
private final PersistenceContextType scope;
private ThreadLocal<EntityManager> thread = new ThreadLocal<EntityManager>();
private ReentrantLock lock = new ReentrantLock();
protected PersistenceService(String unit, EntityManagerFactory emf, boolean managed,
PersistenceContextType scope) {
this.unitName = unit;
this.emf = emf;
this.isManaged = managed;
this.scope = scope;
}
public final EntityManagerFactory getUnit() {
return emf;
}
public final String getUnitName() {
return unitName;
}
public final boolean isManaged() {
return isManaged;
}
public final PersistenceContextType getContextType() {
return scope;
}
/**
* Gets an entity manager associated with the current thread. If the
* current thread is not associated with any entity manager or the
* associated entity manager has been closed, creates a new entity manager
* and associates with the current thread.
*
* @return an entity manager associated with the current thread.
*/
protected EntityManager getEntityManager() {
try {
lock.lock();
EntityManager em = thread.get();
if (em == null || !em.isOpen()) {
em = emf.createEntityManager();
thread.set(em);
}
return em;
} finally {
lock.unlock();
}
}
/**
* Creates a new entity manager. The entity manager is not associated with
* the current thread.
*/
protected EntityManager newEntityManager() {
return emf.createEntityManager();
}
/**
* Begins a transaction on the current thread. If the thread is associated
* with a persistence context, then a transaction is started if necessary.
* If the thread is not associated with a persistence context, then a new
* context is created, associated with the thread, new transaction is
* started.
*
* @see #getEntityManager()
*/
protected EntityManager begin() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
em.joinTransaction();
} else {
if (!em.getTransaction().isActive()) {
em.getTransaction().begin();
}
}
return em;
} finally {
lock.unlock();
}
}
/**
* Commits a transaction on the current thread.
*/
protected void commit() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
em.flush();
} else {
assertActive();
em.getTransaction().commit();
}
if (scope == PersistenceContextType.TRANSACTION) {
em.clear();
}
} finally {
lock.unlock();
}
}
/**
* Rolls back a transaction on the current thread.
*/
protected void rollback() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
} else {
em.getTransaction().rollback();
}
if (scope == PersistenceContextType.TRANSACTION) {
em.clear();
}
} finally {
lock.unlock();
}
}
/**
* Assert current thread is associated with an active transaction.
*/
protected void assertActive() {
EntityManager em = thread.get();
String thread = Thread.currentThread().getName();
assertTrue("No persistent context is associated with " + thread, em != null);
assertTrue("Persistent context " + em + " associated with " + thread + " has been closed", em.isOpen());
if (!isManaged) {
assertTrue("Persistent context " + em + " associated with " + thread + " has no active transaction",
em.getTransaction().isActive());
}
}
protected void assertTrue(String s, boolean p) {
if (!p) {
System.err.println(s);
throw new RuntimeException(s);
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import javax.persistence.Query;
/**
* Decorates an executable query.
* Concrete decorative action can be binding parameters, limiting the result range etc.
*
* @author Pinaki Poddar
*
*/
public interface QueryDecorator {
void decorate(Query q);
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
/**
* Decorates a query by binding parameters.
*
* @author Pinaki Poddar
*
*/
public class QueryParameterBinder implements QueryDecorator {
private final Object[] params;
/**
* Construct a parameter binder with the given parameters.
*
* @param params
*/
public QueryParameterBinder(Object...params) {
this.params = params;
}
@Override
public void decorate(Query query) {
if (params == null)
return;
for (int i = 0; i < params.length; i += 2) {
if (params[i] instanceof Integer) {
query.setParameter((Integer)params[i], params[i+1]);
} else if (params[i] instanceof String) {
query.setParameter((String)params[i], params[i+1]);
} else {
throw new IllegalArgumentException("Parameter index " + params[i] +
" is neither and integer nor String");
}
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2010 Pinaki Poddar
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.server;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContextType;
/**
* A container of persistence units. Typically a JEE container will create, manage and inject
* the persistence units to the user artifacts.
*
* @author Pinaki Poddar
*
*/
public class ServiceFactory {
private static final Map<String, OpenBookService> _services =
new HashMap<String, OpenBookService>();
/**
* Creates a persistence unit of given name configured with the given
* name-value parameters.
*
* @param unit name of the persistence unit. A <code>META-INF/persistence.xml</code> must be
* available with the same unit name in the classpath.
*/
public synchronized static OpenBookService getService(String unit) {
OpenBookService service = _services.get(unit);
if (service == null) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(unit);
service = new OpenBookServiceImpl(unit, emf, false, PersistenceContextType.TRANSACTION);
_services.put(unit, service);
}
return service;
}
}

View File

@ -0,0 +1,289 @@
/*
* Copyright 2010-2012 Pinaki Poddar
*
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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 openbook.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* A set of static utility functions to read properties from file, manage properties with multi-part keys,
* array style properties etc.
*
* @author Pinaki Poddar
*
*/
public class PropertyHelper {
/**
* Filter the properties by the given name.
*
* @param name a part of the key
* @param prefix if true, property key must begin with the given name. Otherwise, the key merely contains the
* name to qualify.
* @param
*
* @return key-value pairs that match.
*/
public static Map<String,Object> filter(Map<String,Object> props, String name, boolean prefix,
boolean includeArrays) {
Map<String, Object> result = new HashMap<String, Object>();
for (String key : props.keySet()) {
if (key == null)
continue;
boolean match = prefix ? key.startsWith(name) : (key.indexOf(name) != -1);
if (match && !isArray(key)) {
result.put(key, props.get(key));
}
}
if (includeArrays) {
Map<String,List<Object>> arrayProperties = filterArrayKeys(props, name, prefix);
result.putAll(arrayProperties);
}
return result;
}
/**
* Select only those property keys which ends with an array marker such as <code>openjpa.DataCache[1]</code>.
* The multiple values of the property is inserted into the resultant map as a List of object against the
* original key.
* <br>
* For example, if the original map had three key-value pairs as
* <LI><code>openjpa.DataCache[1]=true</code>
* <LI><code>openjpa.DataCache[2]=false</code>
* <LI><code>openjpa.DataCache[3]=default</code>
* <br>
* Then that will result into a single entry in the resultant Map under the key <code>openjpa.DataCache</code>
* with a value as a List of three elements namely <code>{true, false, default}</code>. The array index values
* are not significant other than they must all be different for the same base key.
*
* @param name part of the property key
* @param prefix does the name must appear as a prefix?
*
* @return key-value pairs that match.
*/
public static Map<String,List<Object>> filterArrayKeys(Map<String,Object> props, String name, boolean prefix) {
Map<String, List<Object>> result = new HashMap<String, List<Object>>();
for (String key : props.keySet()) {
boolean match = prefix ? key.startsWith(name) : (key.indexOf(name) != -1);
if (match && isArray(key)) {
String realKey = removeArray(key);
List<Object> values = result.get(realKey);
if (values == null) {
values = new ArrayList<Object>();
result.put(realKey, values);
}
values.add(props.get(key));
}
}
return result;
}
/**
* Load properties from the given name resource.
* The given named resource is first looked up as resource on the current thread's context
* and if not found as a file input.
*
* @param resource name a of resource.
*
* @return empty properties if no resource found.
*/
public static Map<String,Object> load(String resource) {
Properties p = new Properties();
try {
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
if (stream == null) {
stream = new FileInputStream(resource);
}
p.load(stream);
} catch (Exception e) {
System.err.println("Error reading " + resource + " due to " + e);
}
return toMap(p);
}
/**
* Affirm if the given resource is available either as a resource in the current thread's context classpath
* or as a file.
*
*/
public static boolean canFind(String resource) {
if (resource == null)
return false;
InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
if (stream != null)
return true;
return new File(resource).exists();
}
public static Map<String, Object> toMap(Properties p) {
Map<String, Object> result = new HashMap<String, Object>();
for (Object k : p.keySet()) {
result.put(k.toString(), p.get(k));
}
return result;
}
/**
* Overwrites any key-value pair in the given map for which a System property is available
* @param original properties to be overwritten
* @return the original property overwritten with System properties
*/
public static Map<String, Object> overwriteWithSystemProperties(Map<String, Object> original) {
Properties properties = System.getProperties();
for (Object syskey : properties.keySet()) {
if (original.containsKey(syskey)) {
original.put(syskey.toString(), properties.get(syskey));
}
}
return original;
}
public static int getInteger(Map<String,Object> props, String key, int def) {
int result = def;
try {
Object value = props.get(key);
if (value != null)
result = Integer.parseInt(value.toString());
} catch (NumberFormatException nfe) {
}
return result;
}
public static double getDouble(Map<String,Object> props, String key, double def) {
double result = def;
try {
Object value = props.get(key);
if (value != null)
result = Double.parseDouble(value.toString());
} catch (NumberFormatException nfe) {
}
return result;
}
public static String getString(Map<String,Object> props, String key, String def) {
Object value = props.get(key);
if (value != null)
return value.toString();
return def;
}
public static List<String> getStringList(Map<String,Object> props, String key) {
return getStringList(props, key, Collections.EMPTY_LIST);
}
public static List<String> getStringList(Map<String,Object> props, String key, List<String> def) {
Object value = props.get(key);
if (value != null)
return Arrays.asList(value.toString().split("\\,"));
return def;
}
/**
* Affirms if the given string using array [] symbol at the end.
*
* @param key a string to check for array symbol.
*/
private static boolean isArray(String key) {
if (key == null || key.length() < 3 || !key.endsWith("]"))
return false;
int i = key.indexOf("[");
if (i == -1 || i != key.lastIndexOf("["))
return false;
String index = key.substring(i+1,key.length()-1);
try {
Integer.parseInt(index);
} catch (NumberFormatException e) {
System.err.println("Bad index " + index + " in " + key);
return false;
}
return true;
}
private static String removeArray(String key) {
int i = key.indexOf("[");
return key.substring(0,i);
}
public static Set<String> getSubsectionKeys(Set<String> keys, String section) {
String prefix = asPrefix(section);
Set<String> subsections = new HashSet<String>();
for (String key : keys) {
if (key.startsWith(prefix)) {
subsections.add(prefix + getPrefix(key.substring(prefix.length())));
}
}
return subsections;
}
private static final String DOT = ".";
private static String asPrefix(String s) {
if (s.endsWith(DOT)) return s;
return s + DOT;
}
public static String getPrefix(String s) {
int i = s.indexOf(DOT);
return (i == -1) ? s : s.substring(0, i);
}
/**
* Get the portion of the given map whose key has the given section at prefix.
*
* @param props a set of name-value pair
* @param section a string representing section of a key
*
* @return a new map with only the keys that starts with the given section.
*/
public static Map<String,Object> getSection(Map<String,Object> props, String section) {
return getSection(props, section, false);
}
/**
* Get the portion of the given map whose key has the given section at prefix.
*
* @param props a set of name-value pair
* @param section a string representing section of a key
* @param retain if true the key of resultant map is same as the original map. Otherwise
* the resultant map keys are without the section prefix.
*
* @return the map with only the keys that starts with the given section.
*/
public static Map<String,Object> getSection(Map<String,Object> props, String section, boolean retain) {
Map<String,Object> result = new HashMap<String, Object>(props);
Set<String> keys = props.keySet();
String prefix = asPrefix(section);
for (String key : keys) {
if (key.startsWith(prefix)) {
String newKey = retain ? key : key.substring(prefix.length());
result.put(newKey, props.get(key));
}
}
return result;
}
}

View File

@ -0,0 +1,95 @@
package openbook.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* A set of static utility functions for simulating pseudo-randomness.
*
* @author Pinaki Poddar
*
*/
public class Randomizer {
private static final Random rng = new Random(System.currentTimeMillis());
private static final int MIN_ALPHA = (int)'A';
private static final int MAX_ALPHA = (int)'Z';
/**
* Returns true with a probability of p.
*/
public static boolean probability(double p) {
return rng.nextDouble() < p;
}
/**
* Picks a random number between 0 (inclusive) and N (exclusive).
*/
public static int random(int n) {
return rng.nextInt(n);
}
/**
* Picks a uniformly distributed random integer within the given range.
*/
public static int random(int min, int max) {
return min + rng.nextInt(max-min);
}
public static double random(double min, double max) {
return min + rng.nextDouble()*(max-min);
}
/**
* Generates a random alphanumeric String with each segment separated by a dash.
*/
public static String randomString(int...segments) {
StringBuffer tmp = new StringBuffer();
for (int s : segments) {
tmp.append(tmp.length() == 0 ? (char)random(MIN_ALPHA, MAX_ALPHA) : '-');
for (int j = 0; j < s; j++)
tmp.append(random(10));
}
return tmp.toString();
}
/**
* Picks a random element from the given list.
*/
public static <T> T selectRandom(List<T> list) {
if (list == null || list.isEmpty())
return null;
if (list.size() == 1)
return list.get(0);
return list.get(random(list.size()));
}
/**
* Selects n elements randomly from the given list.
* @param <T>
* @param list
* @param n
* @return
*/
public static <T> List<T> selectRandom(List<T> list, int n) {
if (list == null || list.isEmpty())
return Collections.emptyList();
Set<Integer> indices = new HashSet<Integer>();
List<T> selected = new ArrayList<T>();
int m = list.size();
if (n >= m) {
selected.addAll(list);
} else {
while (indices.size() < n) {
indices.add(random(m));
}
for (Integer index : indices) {
selected.add(list.get(index));
}
}
return selected;
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Common utility classes used by both OpenBook service and client.
</body>
</html>

View File

@ -0,0 +1,92 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html><head>
<title></title>
<style type="text/css">
<!--code { font-family: Courier New, Courier; font-size: 14pt; margin: 0px; }-->
</style>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head><body>
<!-- ======================================================== -->
<!-- = Java Sourcecode to HTML automatically converted code = -->
<!-- = Java2Html Converter 5.0 [2006-02-26] by Markus Gebhard markus@jave.de = -->
<!-- = Further information: http://www.java2html.de = -->
<div align="left" class="java">
<table border="0" cellpadding="3" cellspacing="0" bgcolor="#ffffff">
<tr>
<!-- start source code -->
<td nowrap="nowrap" valign="top" align="left">
<code>
<font color="#808080">01</font>&nbsp;<font color="#3f7f5f">/*</font><br />
<font color="#808080">02</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;Copyright&nbsp;2010-2012&nbsp;Pinaki&nbsp;Poddar</font><br />
<font color="#808080">03</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*</font><br />
<font color="#808080">04</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*</font><br />
<font color="#808080">05</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;Licensed&nbsp;under&nbsp;the&nbsp;Apache&nbsp;License,&nbsp;Version&nbsp;2.0&nbsp;(the&nbsp;&#34;License&#34;);</font><br />
<font color="#808080">06</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;you&nbsp;may&nbsp;not&nbsp;use&nbsp;this&nbsp;file&nbsp;except&nbsp;in&nbsp;compliance&nbsp;with&nbsp;the&nbsp;License.</font><br />
<font color="#808080">07</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;You&nbsp;may&nbsp;obtain&nbsp;a&nbsp;copy&nbsp;of&nbsp;the&nbsp;License&nbsp;at</font><br />
<font color="#808080">08</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*</font><br />
<font color="#808080">09</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;http://www.apache.org/licenses/LICENSE-2.0</font><br />
<font color="#808080">10</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*</font><br />
<font color="#808080">11</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;Unless&nbsp;required&nbsp;by&nbsp;applicable&nbsp;law&nbsp;or&nbsp;agreed&nbsp;to&nbsp;in&nbsp;writing,&nbsp;software</font><br />
<font color="#808080">12</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;distributed&nbsp;under&nbsp;the&nbsp;License&nbsp;is&nbsp;distributed&nbsp;on&nbsp;an&nbsp;&#34;AS&nbsp;IS&#34;&nbsp;BASIS,</font><br />
<font color="#808080">13</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;WITHOUT&nbsp;WARRANTIES&nbsp;OR&nbsp;CONDITIONS&nbsp;OF&nbsp;ANY&nbsp;KIND,&nbsp;either&nbsp;express&nbsp;or&nbsp;implied.</font><br />
<font color="#808080">14</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;See&nbsp;the&nbsp;License&nbsp;for&nbsp;the&nbsp;specific&nbsp;language&nbsp;governing&nbsp;permissions&nbsp;and</font><br />
<font color="#808080">15</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f7f5f">*&nbsp;&nbsp;limitations&nbsp;under&nbsp;the&nbsp;License.</font><br />
<font color="#808080">16</font>&nbsp;<font color="#3f7f5f">*/</font><br />
<font color="#808080">17</font>&nbsp;<font color="#7f0055"><b>package&nbsp;</b></font><font color="#000000">jpa.tools.swing;</font><br />
<font color="#808080">18</font>&nbsp;<font color="#ffffff"></font><br />
<font color="#808080">19</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">java.awt.Color;</font><br />
<font color="#808080">20</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">java.awt.GridLayout;</font><br />
<font color="#808080">21</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">java.awt.image.BufferedImage;</font><br />
<font color="#808080">22</font>&nbsp;<font color="#ffffff"></font><br />
<font color="#808080">23</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.persistence.metamodel.Attribute;</font><br />
<font color="#808080">24</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.swing.BorderFactory;</font><br />
<font color="#808080">25</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.swing.ImageIcon;</font><br />
<font color="#808080">26</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.swing.JComponent;</font><br />
<font color="#808080">27</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.swing.JLabel;</font><br />
<font color="#808080">28</font>&nbsp;<font color="#7f0055"><b>import&nbsp;</b></font><font color="#000000">javax.swing.JPanel;</font><br />
<font color="#808080">29</font>&nbsp;<font color="#ffffff"></font><br />
<font color="#808080">30</font>&nbsp;<font color="#3f5fbf">/**</font><br />
<font color="#808080">31</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f5fbf">*&nbsp;Displays&nbsp;color&nbsp;codes&nbsp;of&nbsp;each&nbsp;attribute&nbsp;type.</font><br />
<font color="#808080">32</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f5fbf">*&nbsp;</font><br />
<font color="#808080">33</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f5fbf">*&nbsp;</font><font color="#7f9fbf">@author&nbsp;</font><font color="#3f5fbf">Pinaki&nbsp;Poddar</font><br />
<font color="#808080">34</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f5fbf">*</font><br />
<font color="#808080">35</font>&nbsp;<font color="#ffffff">&nbsp;</font><font color="#3f5fbf">*/</font><br />
<font color="#808080">36</font>&nbsp;<font color="#646464">@SuppressWarnings</font><font color="#000000">(</font><font color="#2a00ff">&#34;serial&#34;</font><font color="#000000">)</font><br />
<font color="#808080">37</font>&nbsp;<font color="#7f0055"><b>public&nbsp;class&nbsp;</b></font><font color="#000000">AttributeLegendView&nbsp;</font><font color="#7f0055"><b>extends&nbsp;</b></font><font color="#000000">JPanel&nbsp;</font><font color="#000000">{</font><br />
<font color="#808080">38</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><br />
<font color="#808080">39</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>public&nbsp;</b></font><font color="#000000">AttributeLegendView</font><font color="#000000">()&nbsp;{</font><br />
<font color="#808080">40</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>super</b></font><font color="#000000">(</font><font color="#7f0055"><b>true</b></font><font color="#000000">)</font><font color="#000000">;</font><br />
<font color="#808080">41</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">setBorder</font><font color="#000000">(</font><font color="#000000">BorderFactory.createTitledBorder</font><font color="#000000">(</font><font color="#2a00ff">&#34;Attribute&nbsp;Legends&#34;</font><font color="#000000">))</font><font color="#000000">;</font><br />
<font color="#808080">42</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">setLayout</font><font color="#000000">(</font><font color="#7f0055"><b>new&nbsp;</b></font><font color="#000000">GridLayout</font><font color="#000000">(</font><font color="#990000">0</font><font color="#000000">,</font><font color="#990000">3</font><font color="#000000">))</font><font color="#000000">;</font><br />
<font color="#808080">43</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">add</font><font color="#000000">(</font><font color="#000000">createColoredLabel</font><font color="#000000">(</font><font color="#2a00ff">&#34;IDENTITY&#34;</font><font color="#000000">,&nbsp;Color.RED</font><font color="#000000">))</font><font color="#000000">;</font><br />
<font color="#808080">44</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">add</font><font color="#000000">(</font><font color="#000000">createColoredLabel</font><font color="#000000">(</font><font color="#2a00ff">&#34;VERSION&#34;</font><font color="#000000">,&nbsp;Color.DARK_GRAY</font><font color="#000000">))</font><font color="#000000">;</font><br />
<font color="#808080">45</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>for&nbsp;</b></font><font color="#000000">(</font><font color="#000000">Attribute.PersistentAttributeType&nbsp;type&nbsp;:&nbsp;Attribute.PersistentAttributeType.values</font><font color="#000000">())&nbsp;{</font><br />
<font color="#808080">46</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">add</font><font color="#000000">(</font><font color="#000000">createColoredLabel</font><font color="#000000">(</font><font color="#000000">type.toString</font><font color="#000000">()</font><font color="#000000">.replace</font><font color="#000000">(</font><font color="#990000">'_'</font><font color="#000000">,&nbsp;</font><font color="#990000">'&nbsp;'</font><font color="#000000">)</font><font color="#000000">,&nbsp;MetamodelHelper.getColor</font><font color="#000000">(</font><font color="#000000">type</font><font color="#000000">)))</font><font color="#000000">;</font><br />
<font color="#808080">47</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><br />
<font color="#808080">48</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">}</font><br />
<font color="#808080">49</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><br />
<font color="#808080">50</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">JComponent&nbsp;createColoredLabel</font><font color="#000000">(</font><font color="#000000">String&nbsp;text,&nbsp;Color&nbsp;c</font><font color="#000000">)&nbsp;{</font><br />
<font color="#808080">51</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>int&nbsp;</b></font><font color="#000000">width&nbsp;&nbsp;=&nbsp;</font><font color="#990000">40</font><font color="#000000">;</font><br />
<font color="#808080">52</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>int&nbsp;</b></font><font color="#000000">height&nbsp;=&nbsp;</font><font color="#990000">20</font><font color="#000000">;</font><br />
<font color="#808080">53</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">BufferedImage&nbsp;bimage&nbsp;=&nbsp;</font><font color="#7f0055"><b>new&nbsp;</b></font><font color="#000000">BufferedImage</font><font color="#000000">(</font><font color="#000000">width,&nbsp;height,&nbsp;BufferedImage.TYPE_INT_RGB</font><font color="#000000">)</font><font color="#000000">;&nbsp;</font><br />
<font color="#808080">54</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>for&nbsp;</b></font><font color="#000000">(</font><font color="#7f0055"><b>int&nbsp;</b></font><font color="#000000">i&nbsp;=&nbsp;</font><font color="#990000">0</font><font color="#000000">;&nbsp;i&nbsp;&lt;&nbsp;width;&nbsp;i++</font><font color="#000000">)</font><br />
<font color="#808080">55</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>for&nbsp;</b></font><font color="#000000">(</font><font color="#7f0055"><b>int&nbsp;</b></font><font color="#000000">j&nbsp;=&nbsp;</font><font color="#990000">0</font><font color="#000000">;&nbsp;j&nbsp;&lt;&nbsp;height;&nbsp;j++</font><font color="#000000">)</font><br />
<font color="#808080">56</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">bimage.setRGB</font><font color="#000000">(</font><font color="#000000">i,&nbsp;j,&nbsp;c.getRGB</font><font color="#000000">())</font><font color="#000000">;</font><br />
<font color="#808080">57</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">JLabel&nbsp;label&nbsp;=&nbsp;</font><font color="#7f0055"><b>new&nbsp;</b></font><font color="#000000">JLabel</font><font color="#000000">(</font><font color="#000000">text,&nbsp;</font><font color="#7f0055"><b>new&nbsp;</b></font><font color="#000000">ImageIcon</font><font color="#000000">(</font><font color="#000000">bimage</font><font color="#000000">)</font><font color="#000000">,&nbsp;JLabel.LEADING</font><font color="#000000">)</font><font color="#000000">;</font><br />
<font color="#808080">58</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#7f0055"><b>return&nbsp;</b></font><font color="#000000">label;</font><br />
<font color="#808080">59</font>&nbsp;<font color="#ffffff">&nbsp;&nbsp;&nbsp;&nbsp;</font><font color="#000000">}</font><br />
<font color="#808080">60</font>&nbsp;<font color="#ffffff"></font><br />
<font color="#808080">61</font>&nbsp;<font color="#000000">}</font></code>
</td>
<!-- end source code -->
</tr>
</table>
</div>
<!-- = END of automatically generated HTML code = -->
<!-- ======================================================== -->
</body></html>