Flat file parsing : when some fields contain the delimiter - java

I have a spring-batch application that reads a file with this reader :
<bean id="tradeItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
<property name="resource">
<bean class="org.springframework.core.io.FileSystemResource">
<constructor-arg value="${input.file.path}/#{jobExecutionContext['trades']}" type="java.lang.String"/>
</bean>
</property>
<property name="linesToSkip" value="1" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<!-- split it -->
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<beans:property name="strict" value="false" />
<beans:property name="includedFields" value="0,2,3,6" />
<property name="names"
value="field0,field2,field3,field6" />
</bean>
</property>
<property name="fieldSetMapper">
<bean
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="trade" />
</bean>
</property>
</bean>
</property>
</bean>
The fields are delimited by a comma ,, and here is the catch : some fields look like [LON, TGT] and the line ends up wrongly parsed, because of the comma inside the square brackets.
Example :
Input : Global,,VERIFIED,[LON, TGT],ERerd,3456585,QTR,20190929,20231020
Desired output : Global, VERIFIED, [LON, TGT], QTR
Actual output : Global, VERIFIED, [LON, 3456585
How can I achieve that ? I don't have control over the input file.
EDIT
This is not a duplicate, as the proposed solution would not work : here we don't have a single quote-character, but we have 2 different ones, the opening bracket and the closing bracket.

As explained by Luca Basso Ricci, my input csv is invalid, but I still have to deal with it because I have no control over it.
So I wrote my own delimited line tokenizer, which is just the DelimitedLineTokenizer with a rewritten isDelimiter() method, and replaced it in the conf file :
private boolean isDelimiter(char[] chars, int i, String token, int endIndexLastDelimiter) {
boolean result = false;
int openingBrackets = StringUtils.countOccurrencesOf(new String(Arrays.copyOfRange(chars, 0, i)), "[");
int closingBrackets = StringUtils.countOccurrencesOf(new String(Arrays.copyOfRange(chars, 0, i)), "]");
boolean inBrackets = (openingBrackets - closingBrackets > 0);
if ((i - endIndexLastDelimiter >= this.delimiter.length()) &&
(i >= token.length() - 1)) {
String end = new String(chars, i - token.length() + 1, token.length());
if (token.equals(end)) {
result = !inBrackets;
}
}
return result;
}

Related

Perl - Convert a nested XML format to Java with recursion

I need to convert a nested XML format as below to Java using Perl:
<invoke name="name1" operation="operation1" displayName="Invoke1" id="6">
<input>
<parameter name="Value" variable="Value"/>
<parameter name="ID" variable="ID"/>
</input>
<output>
<parameter name="Return" variable="Return"/>
</output>
</invoke>
<switch name="..." displayName="..." id="13">
<case id="14">
<condition expressionLanguage="..."><![CDATA[(c1)]]></condition>
</case>
<otherwise id="106">
<switch name="..." displayName="..." id="15">
<case id="16">
<condition expressionLanguage="..."><![CDATA[(c2)]]></condition>
<switch name="..." displayName="..." id="19">
<case id="20">
<condition expressionLanguage="..."><![CDATA[(c3) >0)]]></condition>
</case>
<otherwise id="106"> </otherwise>
</switch>
</case>
<otherwise id="107">
<switch name="..." displayName="..." id="33">
<case id="64">
<condition expressionLanguage="..."><![CDATA[(c4)]]></condition>
</case>
<otherwise id="108"> </otherwise>
</switch>
</otherwise>
</switch>
</otherwise>
</switch>
The expected output as the following:
<invoke name="name1" operation="operation1" displayName="Invoke1" id="6">
<input>
<parameter name="Value" variable="Value"/>
<parameter name="ID" variable="ID"/>
</input>
<output>
<parameter name="Return" variable="Return"/>
</output>
</invoke>
if(c1) {
}else{
if(c2) {
if(c3) {
}else{
}
}else{
if(c4) {
}else{
}
}
}
I think that it may be implemented using 4 steps:
Read XML file -> get the first switch1 block -> convert to if--else
Get case1 block and otherwise1 block of switch1 block
Implement recursion from step1 for case1 block and otherwise1 block
Read the rest of XML file and do the same from s1
It's actually difficult for me to do recursion in this case. Can some Perl experts help me here ?
Here's a solution that uses XML::Parser. I've used Style => 'Subs' because the only events I'm interested in are the start and end of case and otherwise elements, and non-blank character data
The array #indent has another element pushed onto it every time we descend into a block, and the last element is popped off when the block ends. The value of the last element in the array is the number of case elements we've seen so far at this level. That allows us to output else if for all occurrences after the first
I've wrapped the whole of the text in parentheses, because your third condition element contains (c3) >0, which doesn't make sense otherwise
I trust that it's clear enough for you to make any adjustments that you may need
use strict;
use warnings 'all';
use XML::Parser;
my $parser = XML::Parser->new(
Style => 'Subs',
Handlers => { Char => \&handle_char },
);
my #indent = (0);
$parser->parse(*DATA);
sub indent {
' ' x $#indent;
}
sub case {
print indent;
print "else " if $indent[-1] > 0;
print "if ( ";
push #indent, 0;
}
sub case_ {
pop #indent;
++$indent[-1];
print indent, "}\n";
}
sub otherwise {
print indent, "else {\n";
push #indent, 0;
}
sub otherwise_ {
pop #indent;
print indent, "}\n";
}
sub handle_char {
my ($expat, $string) = #_;
print $string, " ) {\n" if $string =~ /\S/;
}
__DATA__
<root>
<switch name="..." displayName="..." id="13">
<case id="14">
<condition expressionLanguage="..."><![CDATA[(c1)]]></condition>
</case>
<otherwise id="106">
<switch name="..." displayName="..." id="15">
<case id="16">
<condition expressionLanguage="..."><![CDATA[(c2)]]></condition>
<switch name="..." displayName="..." id="19">
<case id="20">
<condition expressionLanguage="..."><![CDATA[(c3) >0)]]></condition>
</case>
<otherwise id="106">
</otherwise>
</switch>
</case>
<otherwise id="107">
<switch name="..." displayName="..." id="33">
<case id="64">
<condition expressionLanguage="..."><![CDATA[(c4)]]></condition>
</case>
<otherwise id="108">
</otherwise>
</switch>
</otherwise>
</switch>
</otherwise>
</switch>
</root>
output
if ( (c1) ) {
}
else {
if ( (c2) ) {
if ( (c3) >0) ) {
}
else {
}
}
else {
if ( (c4) ) {
}
else {
}
}
}

Spring batch: partition reader called another time

I have to migrate some data from a database to another and I'm using Spring Batch with partition. The configuration of the job is the following
...
...
<bean id="migrationProcessor" class="it.migrazione.MigrazioneProcessor" scope="step"/>
<bean id="migrationWriter" class="it.migrazione.MigrazioneWriter" scope="step"/>
<bean id="migrationReader" class="it.migrazione.MigrazioneReader" scope="step"/>
<bean id="partitioner" class="it.migrazione.MigrazionePartitioner" />
<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="10" />
<property name="allowCoreThreadTimeOut" value="true" />
</bean>
<job id="migrationJob" xmlns="http://www.springframework.org/schema/batch">
<step id="masterStep">
<partition step="slave" partitioner="partitioner">
<handler grid-size="10" task-executor="threadPoolTaskExecutor" />
</partition>
</step>
</job>
<step id="slave" xmlns="http://www.springframework.org/schema/batch">
<tasklet throttle-limit="1" transaction-manager="transactionManager">
<chunk reader="migrationReader"
processor="migrationProcessor"
writer="migrationWriter"
commit-interval="1"/>
</tasklet>
</step>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
...
...
Job knows how many row must be migrated, so the partitioner creates 10 contexts with a specific range related
for (int threadCount = 1; threadCount <= gridSize; threadCount++) {
if (threadCount == 1)
fromRow = 0;
else
fromRow = toRow + 1;
toRow += delta;
context = new ExecutionContext();
context.putInt("fromRow", fromRow);
context.putInt("toRow", toRow);
context.putString("name", "Processing Thread" + threadCount);
result.put("partition" + threadCount, context);
logger.info("Partition number >> " + threadCount + " from Row#: "
+ fromRow + " to Row#: " + toRow);
}
When I run this job I have some threads that read another time. For example Thread#1 call another time the reader,processor and writer while. I don't understand why, but is it possible to have one thread that executes once the chunk, without checking that read is already called? When the writer related to a specific partition ends, why the thread calls the reader another time? It is like reader doesn't immediately see the changes made by writer.

Clarification regarding Spring Batch Chunk Behaviour

We are using Spring Batch Chunk to read messages from JMS destination and write to a flat file. In this regard, we have below observations,
If the message broker goes down while the reader reads the messages and commit count is not reached, what ever number of messages read so far are being passed to Writer and then batch is going in to FAILED state. Is this the default behaviour of Chunk?
If at all the answer for point 1 is YES, how do we make sure that this partial chunk is not sent to Writer. (To give more background to this issue, we have the JMS Session Transacted in the JMS Template, so when the chunk fails to read complete number of messages equal to Commit Count, all the messages read in the partial chunk are being rolled back to the JMS destination, where as the same partial chunk is being written to file. This is causing duplicates in the file when we restart the batch job).
Any help would be greatly appreciated.
EDIT
The configuration is as shown below,
Chunk:
<batch:step id="step-1" next="step-2">
<batch:tasklet allow-start-if-complete="false">
<batch:chunk reader="jms-reader-1-1" writer="file-writer-1-1" commit-interval="1000">
</batch:chunk>
</batch:step>
Writer (Flat File) :
<bean scope="step" class="o.s.b.i.f.FlatFileItemWriter" id="file-writer-1-1">
<property name="resource" value="file:#{T(com.test.core.BatchConfiguration).BATCH_VFS_LOCAL_TEMP_LOCATION}/#{T(com.test.utils.ThreadContextUtils).getJobInstanceIdAsString()}/AssetMesage"/>
<property name="lineAggregator">
<bean class="o.s.b.i.f.t.DelimitedLineAggregator">
<property name="delimiter" value=","/>
<property name="fieldExtractor">
<bean class="o.s.b.i.f.t.BeanWrapperFieldExtractor">
<property name="names" value="assetId,assetName,assetDesc"/>
</bean>
</property>
</bean>
</property>
</bean>
Reader (JMS):
<bean scope="step" class="com.test.runtime.impl.item.readers.JMSItemReader" id="jms-reader-1-1">
<property name="adapter">
<bean class="com.test.adapter.impl.JMSAdapter">
<property name="resource" ref="JMS.vmsmartbatch02_Regression"/>
<property name="retryerId" value="JMS.vmsmartbatch02_Regression-retryer"/>
</bean>
</property>
<property name="destination" value="#{jobParameters[source1jmsdestination] != null ? jobParameters[source1jmsdestination] : "sourceTopic"}"/><property name="durableSubscriberName" value="sourceTopicDS"/><property name="destinationType" value="Topic"/>
<property name="ackMode" value="#{T(javax.jms.Session).CLIENT_ACKNOWLEDGE}"/>
<property name="maxMessageCount" value="2000"/>
</bean>
EDIT 2
below is the core reader logic I am using,
Reader
public Object read() throws Exception, UnexpectedInputException,
ParseException, NonTransientResourceException {
Object item = null;
try {
if(ackMode != 0 && ackMode >= 1 && ackMode <= 3){
adapter.getResource().setSessionAcknowledgeMode(ackMode);
}
if(maxMessageCount > 0){
ThreadContextUtils.addToExecutionContext("maxMessageCount", maxMessageCount);
if(ThreadContextUtils.getExecutionContext().containsKey("readMessageCount")) {
readMessageCount = ThreadContextUtils.getExecutionContext().getInt("readMessageCount");
}
}
if (TOPIC_KEY.equalsIgnoreCase(destinationType)
&& durableSubscriberName != null) {
item = (Object) adapter.invoke(REC_DS_AND_CONVERT_SELECTED,
OBJECT_CLASS, destination, durableSubscriberName,
receiveTimeout, filter == null ? "" : filter);
} else {
item = (Object) adapter.invoke(REC_AND_CONVERT_SELECTED,
OBJECT_CLASS, destination,
receiveTimeout <= 0 ? adapter.getResource()
.getReceiveTimeout() : receiveTimeout,
(filter == null ? "" : filter));
}
if(maxMessageCount > 0){
if( item !=null){
readMessageCount++;
ThreadContextUtils.addToExecutionContext("readMessageCount", readMessageCount);
}
}
return item;
} finally {
}
}

Disable Jax-WS Apache CXF HTTPConduit Message Logging

I've been stuck on this one the last couple of days to no avail and after a lot of googling and trial and error I'm back at the beginning with no luck.
I'm currently working on a Java Application which connects to a third party via JAX-WS. They provide a WSDL which we run in using the jaxws-maven-plugin to generate the services. Implemented via Spring, HTTPConduit is then used to change the endpoints and provide relevant config (e.g. keystores) for connecting to various environments (e.g. SysTest, UAT, Production etc).
The issue is, I haven't set any logging (in fact removing the two interceptors there previously), however the xml message being sent to the third party is appearing in the logs. This is a major issue as we're sending credit card information to the third parties which can no way be logged for obvious reasons. I can change the log4j properties in order to prevent the logging that way, but that's no way a fix.
Here is some code:
This is our beans file.
<jaxws:client id="client1"
xmlns:hsn="http://example.com"
serviceClass="com.example.Service1"
address="${service1.url}"
endpointName="hsn:service1"/>
<jaxws:client id="client2"
xmlns:hsn="http://example.com"
serviceClass="com.example.Service2"
address="${service2.url}"
endpointName="hsn:service2"/>
<jaxws:client id="client3"
xmlns:hsn="http://example.com"
serviceClass="com.example.Service3"
address="${service3.url}"
endpointName="hsn:service3"/>
<http:conduit name="https://*/.*">
<http:tlsClientParameters disableCNCheck="${service.disable-cn-check}">
<sec:keyManagers keyPassword="${service.keystore.password}">
<sec:keyStore type="JKS" password="${service.keystore.password}"
resource="${service.keystore.name}"/>
</sec:keyManagers>
<sec:trustManagers>
<sec:keyStore type="JKS" password="${service.truststore.password}"
resource="${service.truststore.name}"/>
</sec:trustManagers>
<sec:cipherSuitesFilter>
<sec:include>.*_EXPORT_.*</sec:include>
<sec:include>.*_EXPORT1024_.*</sec:include>
<sec:include>.*_WITH_DES_.*</sec:include>
<sec:include>.*_WITH_AES_.*</sec:include>
<sec:include>.*_WITH_NULL_.*</sec:include>
<sec:exclude>.*_DH_anon_.*</sec:exclude>
</sec:cipherSuitesFilter>
</http:tlsClientParameters>
<http:client AutoRedirect="true" Connection="Keep-Alive"
ConnectionTimeout="${service.max-response-time}"
ReceiveTimeout="${service.max-response-time}"/>
</http:conduit>
<http:conduit name="http://*/.*">
<http:client AutoRedirect="true" Connection="Keep-Alive"
ConnectionTimeout="${service.max-response-time}"
ReceiveTimeout="${service.max-response-time}"/>
</http:conduit>
As you can see there are no logging interceptors or logging explicitly turned on using:
<cxf:bus>
<cxf:features>
<cxf:logging/>
</cxf:features>
</cxf:bus>
The only other related file I can think of is META-INF/cxf/org.apache.cxf.Logger which contains:
org.apache.cxf.common.logging.Slf4jLogger
Which even without the file present doesn't make any changes.
Just so you can see, here is a sample from the logs as well:
15:05:45.742 DEBUG | org.apache.cxf.phase.PhaseInterceptorChain - Invoking handleMessage on interceptor org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor#5e62b59d
15:05:45.742 DEBUG | org.apache.cxf.transport.http.Headers - Accept: */*
15:05:45.743 DEBUG | org.apache.cxf.transport.http.Headers - Connection: Keep-Alive
15:05:45.743 DEBUG | org.apache.cxf.transport.http.Headers - SOAPAction: ""
15:05:45.744 DEBUG | org.apache.cxf.transport.http.HTTPConduit - No Trust Decider for Conduit '{http://example.com}service1.http-conduit'. An afirmative Trust Decision is assumed.
15:05:45.746 DEBUG | org.apache.cxf.transport.http.HTTPConduit - Sending POST Message with Headers to http://localhost:8080/stubs/Service1 Conduit :{http://example.com}service1.http-conduit
15:05:45.746 DEBUG | org.apache.cxf.transport.http.HTTPConduit - Conduit "{http://example.com}service1.http-conduit" Transmit cached message to: http://localhost:8080/stubs/Service1: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>********************HERE LIES THE XML MESSAGE*********************</soap:Body></soap:Envelope>
15:05:45.766 DEBUG | org.apache.cxf.endpoint.ClientImpl - Interceptors contributed by bus: [org.apache.cxf.ws.policy.PolicyInInterceptor#24ec87dc]
15:05:45.767 DEBUG | org.apache.cxf.endpoint.ClientImpl - Interceptors contributed by client: []
15:05:45.767 DEBUG | org.apache.cxf.endpoint.ClientImpl - Interceptors contributed by endpoint: [org.apache.cxf.jaxws.interceptors.WrapperClassInInterceptor#52d1f1fb, org.apache.cxf.jaxws.interceptors.HolderInInterceptor#5565c037, org.apache.cxf.jaxws.interceptors.SwAInInterceptor#b2e86ae, org.apache.cxf.frontend.WSDLGetInterceptor#1ca801a2]
15:05:45.768 DEBUG | org.apache.cxf.endpoint.ClientImpl - Interceptors contributed by binding: [org.apache.cxf.interceptor.AttachmentInInterceptor#1b8c0f3e, org.apache.cxf.interceptor.StaxInInterceptor#83cbd93, org.apache.cxf.binding.soap.interceptor.SoapActionInInterceptor#4bc2021e, org.apache.cxf.interceptor.DocLiteralInInterceptor#2e19266d, org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor#7529d5bf, org.apache.cxf.binding.soap.interceptor.ReadHeadersInterceptor#d902ab1, org.apache.cxf.binding.soap.interceptor.StartBodyInterceptor#73e2d16b, org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor#3023033d, org.apache.cxf.binding.soap.interceptor.MustUnderstandInterceptor#4aa9b27b]
15:05:45.768 DEBUG | org.apache.cxf.endpoint.ClientImpl - Interceptors contributed by databinging: [org.apache.cxf.jaxb.attachment.JAXBAttachmentSchemaValidationHack#331fef77]
15:05:45.769 DEBUG | org.apache.cxf.phase.PhaseInterceptorChain - Chain org.apache.cxf.phase.PhaseInterceptorChain#273221e was created. Current flow:
receive [PolicyInInterceptor, AttachmentInInterceptor]
post-stream [StaxInInterceptor]
read [WSDLGetInterceptor, ReadHeadersInterceptor, SoapActionInInterceptor, StartBodyInterceptor]
pre-protocol [MustUnderstandInterceptor]
post-protocol [CheckFaultInterceptor, JAXBAttachmentSchemaValidationHack]
unmarshal [DocLiteralInInterceptor, SoapHeaderInterceptor]
post-logical [WrapperClassInInterceptor]
pre-invoke [SwAInInterceptor, HolderInInterceptor]
15:05:45.769 DEBUG | org.apache.cxf.phase.PhaseInterceptorChain - Invoking handleMessage on interceptor org.apache.cxf.ws.policy.PolicyInInterceptor#24ec87dc
Few months back I had come across similar problem, where I needed to mask few fields of my xml
The CustomLoginInterceptor
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingMessage;
public class KPLogInInterceptor extends LoggingInInterceptor {
#Override
protected String formatLoggingMessage(LoggingMessage loggingMessage) {
String str = loggingMessage.toString();
String output = maskPasswords(str);
//output = maskRestPasswords(output);
return(output);
}
private String maskPasswords(String str) {
// String str =
// "<password1>asdasdad</password1><Password3></Password3><Password5/><PassWord6>fdsfsf</PassWord6>";
final String[] keys = { "password", "authpass", "accountnumber", "authphrase" };
for (String key : keys) {
int beginIndex = 0;
int lastIndex = -1;
boolean emptyPass = false;
boolean multiline = false;
if(key.equals("authphrase") || key.equals("authpass"))
{
//when lines are in multiplelines say <name>authphrase</name><value>vals</value>
multiline = true;
}
while (beginIndex != -1
&& (beginIndex = StringUtils.indexOfIgnoreCase(str, key,
beginIndex)) > 0) {
if(multiline){
beginIndex = StringUtils.indexOfIgnoreCase(str, "value", beginIndex);
}
beginIndex = StringUtils.indexOf(str, ">", beginIndex);
if (beginIndex != -1) {
char ch = str.charAt(beginIndex - 1);
if (ch == '/') {
emptyPass = true;
}
if (!emptyPass) {
lastIndex = StringUtils.indexOf(str, "<", beginIndex);
if (lastIndex != -1) {
String overlay = "*";
String str2 = StringUtils.substring(str,
beginIndex + 1, lastIndex);
if (str2 != null && str2.length() > 1) {
overlay = StringUtils.rightPad(overlay,
str2.length(), "*");
str = StringUtils.overlay(str, overlay,
beginIndex + 1, lastIndex);
}
}
}
if (emptyPass) {
emptyPass = false;
lastIndex = beginIndex + 1;
} else {
if (lastIndex != -1) {
lastIndex = StringUtils
.indexOf(str, ">", lastIndex);
}
}
}
beginIndex = lastIndex;
}
}
return str;
}
}
And the cxf config xml
<bean id="kpInInterceptor" class="com.kp.swasthik.KPLogInInterceptor"></bean>
<bean id="kpOutInterceptor" class="com.kp.swasthik.KPLogOutInterceptor"></bean>
<cxf:bus>
<cxf:inInterceptors>
<ref bean="kpInInterceptor" />
</cxf:inInterceptors>
<cxf:outInterceptors>
<ref bean="kpOutInterceptor" />
</cxf:outInterceptors>
<cxf:outFaultInterceptors>
<ref bean="kpOutInterceptor" />
</cxf:outFaultInterceptors>
<cxf:inFaultInterceptors>
<ref bean="kpInInterceptor" />
</cxf:inFaultInterceptors>
</cxf:bus>
You need to create one more class that extends LogOutInterceptor
EDIT
Create class the sets the loglevel to INFO for
public class KPLogicSupresser {
public void kpinit(){
LogManager.getLogger(HTTPConduit.class).setLevel(Level.INFO);
}
}
And create a bean in CXF configuration file
<bean id="kpLog4Jsupresser" class="com.kp.swasthik.KPLogicSupresser" init-method="kpinit" ></bean>
Just add logback.xml file in your class path with logger level INFO, it will disable all output from CXF DEBUGS.
Sample File
Filename: logback.xml
Location: src/main/resources (In my project its resources, you can place the file accordingly in your project classpath)
File Content:
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
<resetJUL>true</resetJUL>
</contextListener>
<!-- To enable JMX Management -->
<jmxConfigurator/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.mycompany.subpackage" level="INFO"/>
<root level="INFO">
<appender-ref ref="console"/>
</root>

Parse XML with class information and instantiate via Reflection

I have two XML Files that contain information about some classes. After parsing the XML i want to instantiate these classes via Reflection.
I parse the xml with DOM and Recursion. What i want to know is which is the most generic way to implement it. Which is the optimal way to transfer the information and build the GUI.
I really cannot think anything else expect many IF...ELSE(
like this:
if (node.getNodeName() == "class") {
Class cls = Class.forName(node.getNodeValue());
}
)
statements but i do not think that this is the optimal way.
The dom parser:
for (int count = 0; count < nodeList.getLength(); count++) {
Node tempNode = nodeList.item(count);
// make sure it's element node.
if (tempNode.getNodeType() == Node.ELEMENT_NODE) {
// get node name and value
System.out.println("\nNode Name =" + tempNode.getNodeName() + " [OPEN]");
System.out.println("Node Value =" + tempNode.getNodeValue());
if (tempNode.hasAttributes()) {
// get attributes names and values
NamedNodeMap nodeMap = tempNode.getAttributes();
for (int i = 0; i < nodeMap.getLength(); i++) {
Node node = nodeMap.item(i);
System.out.println("attr name : " + node.getNodeName());
System.out.println("attr value : " + node.getNodeValue());
// System.out.println("Node Value : " +);
if (node.getNodeName() == "class") {
Class cls = Class.forName(node.getNodeValue());
}
}
}
if (tempNode.hasChildNodes()) {
// loop again if has child nodes
printNote(tempNode.getChildNodes());
}
System.out.println("Node Name =" + tempNode.getNodeName() + " [CLOSE]");
}
The XML files looks like this:
<ui-model>
<waui>
<abstract-container wauiId = '1'>
<abstract-button wauiId = '2'></abstract-button>
<abstract-button wauiId = '3'></abstract-button>
<abstract-button wauiId = '4'></abstract-button>
</abstract-container>
</waui>
<wrm>
<wr-item wauiId = '2'>
<abstract-properties>
<abstract-property name='text'>Button1</abstract-property>
</abstract-properties>
<polymorphic-properties>
<polymorphic-instance piId='swingRectButton'>
<polymorphic-property name='width'>100</polymorphic-property>
<polymorphic-property name='height'>50</polymorphic-property>
</polymorphic-instance>
<polymorphic-instance piId='swingRoundButton'>
<polymorphic-property name='radius'>80</polymorphic-property>
<polymorphic-property name='background-color'>red</polymorphic-property>
</polymorphic-instance>
</polymorphic-properties>
</wr-item>
<wr-item wauiId = '3'>
<abstract-properties>
<abstract-property name='text'>Button2</abstract-property>
</abstract-properties>
<polymorphic-properties>
<polymorphic-instance piId='swingRectButton'>
<polymorphic-property name='width'>200</polymorphic-property>
<polymorphic-property name='height'>60</polymorphic-property>
</polymorphic-instance>
</polymorphic-properties>
</wr-item>
<wr-item wauiId = '4'>
<abstract-properties>
<abstract-property name='text'>Button3</abstract-property>
</abstract-properties>
<polymorphic-properties>
<polymorphic-instance piId='swingRoundButton'>
<polymorphic-property name='radius'>9</polymorphic-property>
<polymorphic-property name='background-color'>blue</polymorphic-property>
</polymorphic-instance>
</polymorphic-properties>
</wr-item>
</wrm>
<widget name='abstract-button'>
<abstract-properties>
<property name='text' id='wsl_1'/>
</abstract-properties>
<polymorphic-instances>
<instance name='swingRectButton'>
<polymorphic-properties>
<property name='width' />
<property name='height' />
</polymorphic-properties>
</instance>
<instance name='swingRoundButton'>
<property name='radius' />
<property name='background-color' />
</instance>
</polymorphic-instances>
<polymorphic-instances-api>
<polymorphic-instance id='swingRectButton' class='javax.swing.JButton'>
<property name='text'>
<native-method>setText</native-method>
<param-type>String</param-type>
</property>
<property name='width'>
<native-method>setWidth</native-method>
<param-type>Integer</param-type>
</property>
<property name='height'>
<native-method>setHeight</native-method>
<param-type>Integer</param-type>
</property>
</polymorphic-instance>
<polymorphic-instance id='swingRoundButton' class='gr.epp.aswing.RoundButton'>
<property name='text'>
<native-method>setLabel</native-method>
<param-type>String</param-type>
</property>
<property name='radius'>
<native-method>setRadius</native-method>
<param-type>Integer</param-type>
</property>
<property name='background-color'>
<native-method>setBackgroundColor</native-method>
<param-type>String</param-type>
</property>
</polymorphic-instance>
</polymorphic-instances-api>
I thought about writing this as a comment to your question, but after more thought, I think it is an appropriate answer.
Avoid premature optimization.
If you've already written code that works and you're running into a specific problem then explain that problem. But you should not try to optimize your code unless there is an identifiable problem with it.
See http://c2.com/cgi/wiki?PrematureOptimization

Categories