001: /**********************************************************************
002: Copyright (c) 2004 Erik Bengtson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: 2004 Andy Jefferson - toString(), MetaData, javadocs
017: 2004 Andy Jefferson - added index, and indexed
018: ...
019: **********************************************************************/package org.jpox.metadata;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023: import java.util.StringTokenizer;
024:
025: import org.jpox.util.StringUtils;
026:
027: /**
028: * Representation of Order MetaData - the ordering of the elements of a List.
029: * This caters for 2 types of List.
030: * <ul>
031: * <li><b>indexed list</b> like in JDO2 where we add an index column</li>
032: * <li><b>ordered list</b> like in JPA1 where we use some ordering clause when retrieving</li>
033: * </ul>
034: *
035: * @version $Revision: 1.20 $
036: */
037: public class OrderMetaData extends MetaData implements
038: ColumnMetaDataContainer {
039: /** The name of the column (if specified as input) */
040: final String columnName;
041:
042: /** the columns */
043: final List columns = new ArrayList();
044:
045: /** IndexMetaData. */
046: protected IndexMetaData indexMetaData;
047:
048: /** The indexing value specified as input. */
049: protected IndexedValue indexed = null;
050:
051: /** Name of the field in the element that is the ordering field. */
052: protected final String mappedBy;
053:
054: /**
055: * Ordering when using an "ordered list" where the elements are retrieved in a particular order.
056: * Only used until initialise().
057: */
058: protected String ordering;
059:
060: /** Ordering of fields (when using "ordered List"). */
061: protected FieldOrder[] fieldOrders = null;
062:
063: // -------------------------------------------------------------------------
064: // Fields below here are not represented in the output MetaData. They are
065: // for use internally in the operation of the JDO system. The majority are
066: // for convenience to save iterating through the fields since the fields
067: // are fixed once initialised.
068:
069: /**
070: * Contains the metadata for column
071: */
072: protected ColumnMetaData[] columnMetaData;
073:
074: /**
075: * Constructor to create a copy of the passed metadata using the provided parent.
076: * @param parent The parent
077: * @param omd The metadata to copy
078: */
079: public OrderMetaData(MetaData parent, OrderMetaData omd) {
080: super (parent);
081: this .indexed = omd.indexed;
082: this .columnName = omd.columnName;
083:
084: // TODO Change these to copy rather than reference
085: if (omd.indexMetaData != null) {
086: this .indexMetaData = omd.indexMetaData;
087: }
088: for (int i = 0; i < omd.columns.size(); i++) {
089: addColumn((ColumnMetaData) omd.columns.get(i));
090: }
091: this .mappedBy = omd.mappedBy;
092: this .ordering = omd.ordering;
093: }
094:
095: /**
096: * Constructor when defining an "indexed list" (like JDO2).
097: * @param column Name of column
098: * @param indexed The indexed value
099: * @param mappedBy The field in the element that provides the ordering
100: */
101: public OrderMetaData(final String column, final String indexed,
102: final String mappedBy) {
103: super (null); // Ignore parent
104: this .columnName = (StringUtils.isWhitespace(column) ? null
105: : column);
106: this .indexed = IndexedValue.getIndexedValue(indexed);
107: this .mappedBy = (StringUtils.isWhitespace(mappedBy) ? null
108: : mappedBy);
109: this .ordering = null;
110: }
111:
112: /**
113: * Constructor when defining an "ordered list" (like JPA1)
114: * @param ordering ordering when using an ordered list where the elements are retrieved in a particular order
115: */
116: public OrderMetaData(final String ordering) {
117: super (null); // Ignore parent
118: this .columnName = null;
119: this .mappedBy = null;
120: this .ordering = (StringUtils.isWhitespace(ordering) ? null
121: : ordering);
122: }
123:
124: /**
125: * Method to initialise the object, creating internal convenience arrays.
126: * Initialises all sub-objects.
127: */
128: public void initialise() {
129: if (hasExtension("list-ordering")) {
130: // User has provided JPOX extension "list-ordering" meaning that we use an ordered list
131: // for this collection (like in JPA)
132: String val = getValueForExtension("list-ordering");
133: if (!StringUtils.isWhitespace(val)) {
134: this .ordering = val;
135: }
136: }
137:
138: columnMetaData = new ColumnMetaData[columns.size()];
139: if (columns.size() == 0 && columnName != null) {
140: columnMetaData = new ColumnMetaData[1];
141: columnMetaData[0] = new ColumnMetaData(this , columnName);
142: columnMetaData[0].initialise();
143: } else {
144: columnMetaData = new ColumnMetaData[columns.size()];
145: for (int i = 0; i < columnMetaData.length; i++) {
146: columnMetaData[i] = (ColumnMetaData) columns.get(i);
147: columnMetaData[i].initialise();
148: }
149: }
150:
151: // Interpret the "indexed" value to create our IndexMetaData where it wasn't specified that way
152: if (indexMetaData == null && columnMetaData != null
153: && indexed != null && indexed != IndexedValue.FALSE) {
154: indexMetaData = new IndexMetaData(null, null,
155: (indexed == IndexedValue.UNIQUE) ? "true" : "false");
156: for (int i = 0; i < columnMetaData.length; i++) {
157: indexMetaData.addColumn(columnMetaData[i]);
158: }
159: }
160: if (indexMetaData != null) {
161: indexMetaData.initialise();
162: }
163:
164: if (mappedBy != null) {
165: // Check that the "mapped-by" field exists in the element class
166: AbstractMemberMetaData fmd = (AbstractMemberMetaData) parent;
167: AbstractClassMetaData elementCmd = fmd.getCollection().element.classMetaData;
168: if (elementCmd != null && !elementCmd.hasMember(mappedBy)) {
169: throw new InvalidMetaDataException(LOCALISER, "044137",
170: fmd.getFullFieldName(), elementCmd
171: .getFullClassName(), mappedBy);
172: }
173: }
174:
175: if (ordering != null) {
176: // "ordered List", so split the ordering into its components
177: AbstractMemberMetaData fmd = (AbstractMemberMetaData) parent;
178: AbstractClassMetaData elementCmd = fmd.getCollection().element.classMetaData;
179: if (ordering.equals("#PK")) {
180: // Order using the PK of the element
181: fieldOrders = new FieldOrder[elementCmd
182: .getNoOfPrimaryKeyMembers()];
183: String[] pkFieldNames = elementCmd
184: .getPrimaryKeyMemberNames();
185: int i = 0;
186: for (int pkFieldNum = 0; pkFieldNum < fieldOrders.length; pkFieldNum++) {
187: fieldOrders[i++] = new FieldOrder(
188: pkFieldNames[pkFieldNum]);
189: }
190: } else {
191: // Order using the provided definition
192: StringTokenizer tokeniser = new StringTokenizer(
193: ordering, ",");
194: int num = tokeniser.countTokens();
195: fieldOrders = new FieldOrder[num];
196: int i = 0;
197: while (tokeniser.hasMoreTokens()) {
198: String nextToken = tokeniser.nextToken().trim();
199: String fieldName = null;
200: boolean forward = true;
201: int spacePos = nextToken.indexOf(' ');
202: if (spacePos > 0) {
203: // Of the form "{field} {ordering}"
204: fieldName = nextToken.substring(0, spacePos);
205: String direction = nextToken.substring(
206: spacePos + 1).trim();
207: if (direction.equalsIgnoreCase("DESC")) {
208: forward = false;
209: } else if (!direction.equalsIgnoreCase("ASC")) {
210: throw new InvalidMetaDataException(
211: LOCALISER, "044139", fmd
212: .getFullFieldName(),
213: direction);
214: }
215: } else {
216: // Of the form "{field}"
217: fieldName = nextToken;
218: }
219:
220: if (elementCmd != null
221: && !elementCmd.hasMember(fieldName)) {
222: throw new InvalidMetaDataException(LOCALISER,
223: "044138", fmd.getFullFieldName(),
224: elementCmd.getFullClassName(),
225: fieldName);
226: }
227:
228: // Add the field order
229: fieldOrders[i] = new FieldOrder(fieldName);
230: if (!forward) {
231: fieldOrders[i].setBackward();
232: }
233: i++;
234: }
235: }
236: }
237:
238: setInitialised();
239: }
240:
241: /**
242: * Add a new ColumnMetaData element
243: * @param colmd The Column MetaData
244: */
245: public void addColumn(ColumnMetaData colmd) {
246: columns.add(colmd);
247: colmd.parent = this ;
248: }
249:
250: /**
251: * Mutator for the index MetaData
252: * @param indexMetaData The indexMetaData to set.
253: */
254: public final void setIndexMetaData(IndexMetaData indexMetaData) {
255: this .indexMetaData = indexMetaData;
256: }
257:
258: /**
259: * Convenience method to return if the List is an "indexed List" like in JDO2.
260: * @return Whether the List is indexed (if false means that it is "ordered" (like in JPA1)
261: */
262: public boolean isIndexedList() {
263: return (fieldOrders == null);
264: }
265:
266: /**
267: * Accessor for the field in the element that provides the ordering.
268: * @return Field in the value that provides the ordering.
269: */
270: public String getMappedBy() {
271: return mappedBy;
272: }
273:
274: /**
275: * Accessor for field ordering (if using "ordered List".
276: * @return Field orders
277: */
278: public FieldOrder[] getFieldOrders() {
279: return fieldOrders;
280: }
281:
282: /**
283: * Accessor for the Column MetaData for the columns
284: * @return Returns the columnMetaData.
285: */
286: public final ColumnMetaData[] getColumnMetaData() {
287: return columnMetaData;
288: }
289:
290: /**
291: * Accessor for the column name
292: * @return Returns the column.
293: */
294: public final String getColumnName() {
295: return columnName;
296: }
297:
298: /**
299: * Accessor for indexMetaData
300: * @return Returns the indexMetaData.
301: */
302: public final IndexMetaData getIndexMetaData() {
303: return indexMetaData;
304: }
305:
306: // ------------------------------ Utilities --------------------------------
307:
308: /**
309: * Returns a string representation of the object using a prefix
310: * This can be used as part of a facility to output a MetaData file.
311: * @param prefix prefix string
312: * @param indent indent string
313: * @return a string representation of the object.
314: */
315: public String toString(String prefix, String indent) {
316: // Field needs outputting so generate metadata
317: StringBuffer sb = new StringBuffer();
318: sb.append(prefix).append("<order");
319: if (columnName != null) {
320: sb.append(" column=\"" + columnName + "\"");
321: }
322: if (indexed != null) {
323: sb.append(" indexed=\"" + indexed.toString() + "\"");
324: }
325: if (mappedBy != null) {
326: sb.append(" mapped-by=\"" + mappedBy + "\"");
327: }
328: sb.append(">\n");
329:
330: // Add columns
331: for (int i = 0; i < columns.size(); i++) {
332: ColumnMetaData c = (ColumnMetaData) columns.get(i);
333: sb.append(c.toString(prefix + indent, indent));
334: }
335:
336: // Add index
337: if (indexMetaData != null) {
338: sb.append(indexMetaData.toString(prefix + indent, indent));
339: }
340:
341: // Add extensions
342: sb.append(super .toString(prefix + indent, indent));
343:
344: sb.append(prefix).append("</order>\n");
345: return sb.toString();
346: }
347:
348: /**
349: * Definition of ordering using a field.
350: * Used by "ordered lists".
351: *
352: * @version $Revision: 1.20 $
353: */
354: public static class FieldOrder {
355: String fieldName;
356: boolean forward = true;
357:
358: public FieldOrder(String name) {
359: fieldName = name;
360: }
361:
362: public void setBackward() {
363: forward = false;
364: }
365:
366: public String getFieldName() {
367: return fieldName;
368: }
369:
370: public boolean isForward() {
371: return forward;
372: }
373: }
374: }
|