Configure logging in Google App Engine

Google App Engine for Java is using java.util.logging.Logger to perform the logging. In this tutorial, we show you how to do logging in GAE environment.

1. Logging Example

Example to define a logger in MovieController, and log the messages in different logging levels (info, warning and error)

import java.util.logging.Logger;
//...
 
@Controller
@RequestMapping("/movie")
public class MovieController {
 
private static final Logger log = Logger.getLogger(MovieController.class.getName());
 
@RequestMapping(value="/{name}", method = RequestMethod.GET)
public String getMovie(@PathVariable String name, ModelMap model) {
 
log.info("Information log message.");
 
log.warning("Warning log message.");
 
log.severe("Error log message.");
 
return "page";
 
}
 
}

2. Logging Example

Create a file, logging.properties, set the logging level, and put it in “$project/war/WEB-INF

File : $project/war/WEB-INF/logging.properties

# Set the default logging level for all loggers to WARNING
.level = WARNING

3. appengine-web.xml

Update appengine-web.xml, define system properties tag, and point logging to above “WEB-INF/logging.properties“.

File : $project/war/WEB-INF/appengine-web.xml

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>mkyong-springmvc</application>
<version>1</version>
 
<!-- Configure java.util.logging -->
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
 
</appengine-web-app>

4. Done

In GAE local development environment, all logged message will be display on console. In GAE production environment, you can access the logged messages in your application’s administrator page.

gae java logging gae java logging

Cron job on Google App Engine for Java

In this tutorial, we will show you how to create a scheduler task or cron job on Google App Engine, for Java. In GAE, cron job uses HTTP GET request to call an URL, the duration of the cron job running time is limited to run up to 10 minutes only, if excess, GAE kill your job.

To create a cron job on GAE, just define cron jobs in a file “cron.xml“, and put in the “WEB-INF” folder.

Cron Job Tutorial

Now, we will create a cron job on GAE, and schedule call an URL from Spring MVC REST example.

  1. Google App Engine Java SDK 1.6.3.1
  2. Spring 3.1.1
  3. JDK 1.6
  4. Eclipse 3.7 + Google Plugin for Eclipse

1. Spring Controller

A simple Spring REST controller.

package com.mkyong.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
@RequestMapping("/cron")
public class CronController {
 
static int total = 0;
 
@RequestMapping(value="/count", method = RequestMethod.GET)
public String getCount(ModelMap model) {
 
model.addAttribute("total", total);
 
return "list";
 
}
 
//cron job running this
@RequestMapping(value="/addCount/{num}", method = RequestMethod.GET)
public String addCount(@PathVariable int num, ModelMap model) {
 
total += num;
model.addAttribute("total", total);
 
return "list";
 
}
 
}

File : list.jsp – Page to display the total counts.

<html>
<body>
<h1>GAE + Spring 3 MVC REST + CRON Example</h1>
 
<h3>Counts : ${total} </h3>
 
</body>
</html>

2. cron.xml

Create a “cron.xml” and put in the “WEB-INF” folder. This cron job will call URL “/cron/addCount/1” every 1 minutes.

File : cron.xml

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/cron/addCount/1</url>
<description>Add count + 1 every 1 minutes</description>
<schedule>every 1 minutes</schedule>
</cron>
</cronentries>

Note
The cron job schedule format is a simple English-like format. Please read this GAE scheduler format for more detail.
every N (hours|mins|minutes) ["from" (time) "to" (time)]

3. Output

Deployed on GAE, you can view the cron job status on application administrator page.

gae cron example gae cron example

URL : http://mkyong-springmvc-cron.appspot.com/cron/count , result after one minutes

cron job on gae cron job on gae

Download Source Code

Due to large file size, all Spring and GAE jars are excluded.

Download – CronJob-GoogleAppEngine-Example.zip (13 KB)

10 Java Regular Expression Examples You Should Know

Regular expression is an art of the programing, it’s hard to debug , learn and understand, but the powerful features are still attract many developers to code regular expression. Let’s explore the following 10 practical regular expression ~ enjoy 🙂

1. Username Regular Expression Pattern

 ^[a-z0-9_-]{3,15}$

^                    # Start of the line
[a-z0-9_-] # Match characters and symbols in the list, a-z, 0-9 , underscore , hyphen
{3,15} # Length at least 3 characters and maximum length of 15
$ # End of the line

==> See the explanation and example here

2. Password Regular Expression Pattern

((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})

(			# Start of group
(?=.*\d) # must contains one digit from 0-9
(?=.*[a-z]) # must contains one lowercase characters
(?=.*[A-Z]) # must contains one uppercase characters
(?=.*[@#$%]) # must contains one special symbols in the list "@#$%"
. # match anything with previous condition checking
{6,20} # length at least 6 characters and maximum of 20
) # End of group

==> See the explanation and example here

3. Hexadecimal Color Code Regular Expression Pattern

^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$

^		 #start of the line
# # must constains a "#" symbols
( # start of group #1
[A-Fa-f0-9]{6} # any strings in the list, with length of 6
| # ..or
[A-Fa-f0-9]{3} # any strings in the list, with length of 3
) # end of group #1
$ #end of the line

==> See the explanation and example here

4. Email Regular Expression Pattern

^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+
(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$

^			#start of the line
[_A-Za-z0-9-]+ # must start with string in the bracket [ ], must contains one or more (+)
( # start of group #1
\\.[_A-Za-z0-9-]+ # follow by a dot "." and string in the bracket [ ], must contains one or more (+)
)* # end of group #1, this group is optional (*)
@ # must contains a "@" symbol
[A-Za-z0-9]+ # follow by string in the bracket [ ], must contains one or more (+)
( # start of group #2 - first level TLD checking
\\.[A-Za-z0-9]+ # follow by a dot "." and string in the bracket [ ], must contains one or more (+)
)* # end of group #2, this group is optional (*)
( # start of group #3 - second level TLD checking
\\.[A-Za-z]{2,} # follow by a dot "." and string in the bracket [ ], with minimum length of 2
) # end of group #3
$ #end of the line

==> See the explanation and example here

5. Image File Extension Regular Expression Pattern

([^\s]+(\.(?i)(jpg|png|gif|bmp))$)

(			#Start of the group #1
[^\s]+ # must contains one or more anything (except white space)
( # start of the group #2
\. # follow by a dot "."
(?i) # ignore the case sensitive checking
( # start of the group #3
jpg # contains characters "jpg"
| # ..or
png # contains characters "png"
| # ..or
gif # contains characters "gif"
| # ..or
bmp # contains characters "bmp"
) # end of the group #3
) # end of the group #2
$ # end of the string
) #end of the group #1

==> See the explanation and example here

6. IP Address Regular Expression Pattern

^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.
([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.([01]?\\d\\d?|2[0-4]\\d|25[0-5])$

^		#start of the line
( # start of group #1
[01]?\\d\\d? # Can be one or two digits. If three digits appear, it must start either 0 or 1
# e.g ([0-9], [0-9][0-9],[0-1][0-9][0-9])
| # ...or
2[0-4]\\d # start with 2, follow by 0-4 and end with any digit (2[0-4][0-9])
| # ...or
25[0-5] # start with 2, follow by 5 and end with 0-5 (25[0-5])
) # end of group #2
\. # follow by a dot "."
.... # repeat with 3 time (3x)
$ #end of the line

==> See the explanation and example here

7. Time Format Regular Expression Pattern

Time in 12-Hour Format Regular Expression Pattern

(1[012]|[1-9]):[0-5][0-9](\\s)?(?i)(am|pm)

(				#start of group #1
1[012] # start with 10, 11, 12
| # or
[1-9] # start with 1,2,...9
) #end of group #1
: # follow by a semi colon (:)
[0-5][0-9] # follow by 0..5 and 0..9, which means 00 to 59
(\\s)? # follow by a white space (optional)
(?i) # next checking is case insensitive
(am|pm) # follow by am or pm

==> See the explanation and example here

Time in 24-Hour Format Regular Expression Pattern

([01]?[0-9]|2[0-3]):[0-5][0-9]

(				#start of group #1
[01]?[0-9] # start with 0-9,1-9,00-09,10-19
| # or
2[0-3] # start with 20-23
) #end of group #1
: # follow by a semi colon (:)
[0-5][0-9] # follow by 0..5 and 0..9, which means 00 to 59

==> See the explanation and example here

8. Date Format (dd/mm/yyyy) Regular Expression Pattern

(0?[1-9]|[12][0-9]|3[01])/(0?[1-9]|1[012])/((19|20)\\d\\d)

(			#start of group #1
0?[1-9] # 01-09 or 1-9
| # ..or
[12][0-9] # 10-19 or 20-29
| # ..or
3[01] # 30, 31
) #end of group #1
/ # follow by a "/"
( # start of group #2
0?[1-9] # 01-09 or 1-9
| # ..or
1[012] # 10,11,12
) # end of group #2
/ # follow by a "/"
( # start of group #3
(19|20)\\d\\d # 19[0-9][0-9] or 20[0-9][0-9]
) # end of group #3

==> See the explanation and example here

9. HTML tag Regular Expression Pattern

<("[^"]*"|'[^']*'|[^'">])*>

<	  	#start with opening tag "<"
( # start of group #1
"[^"]*" # only two double quotes are allow - "string"
| # ..or
'[^']*' # only two single quotes are allow - 'string'
| # ..or
[^'"
>] # cant contains one single quotes, double quotes and ">"
) # end of group #1
* # 0 or more
> #end with closing tag ">"

==> See the explanation and example here

10. HTML links Regular Expression Pattern

HTML A tag Regular Expression Pattern

(?i)<a([^>]+)>(.+?)</a>

(		#start of group #1
?i # all checking are case insensive
) #end of group #1
<a #start with "<a"
( # start of group #2
[^>]+ # anything except (">"), at least one character
) # end of group #2
> # follow by ">"
(.+?) # match anything
</a> # end with "</a>

Extract HTML link Regular Expression Pattern

\s*(?i)href\s*=\s*(\"([^"]*\")|'[^']*'|([^'">\s]+));

\s*			   #can start with whitespace
(?i) # all checking are case insensive
href # follow by "href" word
\s*=\s* # allows spaces on either side of the equal sign,
( # start of group #1
"([^"]*") # only two double quotes are allow - "string"
| # ..or
'[^']*' # only two single quotes are allow - 'string'
| # ..or
([^'"
>]+) # cant contains one single / double quotes and ">"
) # end of group #1

==> See the explanation and example here

Spring Batch Tutorial

spring batch model spring batch model

Photo credit: Spring Source

Spring Batch, is an open source framework for batch processing – execution of a series of jobs. Spring Batch provides classes and APIs to read/write resources, transaction management, job processing statistics, job restart and partitioning techniques to process high-volume of data.

The following Spring Batch tutorials and examples are tested with :

  1. Spring Core 3.2.2.RELEASE
  2. Spring Batch 2.2.0.RELEASE

P.S Spring Batch is part of the Spring Portfolio.

1. Quick Guide

A simple batch job, and run with command line.

  • Spring Batch Hello World Example
    Short description about Spring batch, and create a job to read data from a CSV File, process it, and write it to an XML file (JAXB).
  • Spring Batch TaskletStep example
    Example to cleanup the resources after the batch job has completed.
  • Run Spring Batch Job With CommandLineJobRunner
    Run Spring batch job with command line.

2. ItemReader, ItemProcessor, ItemWriter

Few examples to show the use of Spring batch classes to read/write resources (csv, xml and database).

  • Spring Batch Example – CSV File To MySQL database
    Read data from a CSV file and write it into a MySQL database, job meta is stored in the database.
  • Spring Batch Example – XML File To MongoDB database
    Read data from a XML file (XStream) and write it into a nosql database MongoDB, also unit test the batch job.
  • Spring Batch Example – XML File To CSV File
    Read data from a XML file (JAXB2), process with ItemProcessor and write it into a CSV file. It also shows you how to convert Date and BigDecimal data type to/from object via JAXB2.
  • Spring Batch Example – MySQL Database To XML
    Read data from a MySQL database and write it into an XML file (XStream), also the use of jobParameters.
  • Spring Batch MultiResourceItemReader example
    A ItemReader to read multiple files.

3. Scheduler

Run batch job with scheduler frameworks.

  • Spring Batch + Spring TaskScheduler example
    Spring TaskScheduler to schedule a batch job to run every 5 seconds.
  • Spring Batch + Quartz Scheduler example
    Integrate Quartz to schedule a batch job to run every 10 seconds.

4. Unit Test

  • Spring Batch unit test example
    How to unit test a batch job or individual steps.

5. Advance

  • Spring Batch partitioner example
    Multiple threads to process range of data.
  • Spring Batch listener example
    Examples to intercept the step execution of a batch job.

6. Spring Batch FAQs

Some common questions and answers in Spring Batch.

  • How to convert Date in BeanWrapperFieldSetMapper
  • Spring Batch : A job instance already exists and is complete for parameters={}
  • Spring Batch metadata tables are not created automatically?
  • NoSuchBeanDefinitionException : No qualifying bean of type JobLauncherTestUtils
  • jobParameters cannot be found on object of type BeanExpressionContext
  • Spring Batch official website
  • Spring Batch Reference Documentation
  • Wikipedia : Spring Batch
  • Spring Batch Samples
Next >
Index Spring Batch Hello World Example

GAE : how to output log messages to a file

By default, all logging messages will output to log console. To change the logging settings, find this file – {Google App Engine SDK directory}\google\appengine\tools\dev_appserver_main.py.

File : dev_appserver_main.py – Find following pattern

#...
import getopt
import logging
import os
import signal
import sys
import tempfile
import traceback
 
logging.basicConfig(
level=logging.INFO,
format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
#...

Output to File

In order to output log messages to a file, we can change the configuration of logging in dev_appserver_main.py like in below:

#...
import getopt
import logging
import os
import signal
import sys
import tempfile
import traceback
 
# default , comment out
#logging.basicConfig(
# level=logging.INFO,
# format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
 
# new log settings , output to a file
logging.basicConfig(
filename='/Users/lokjack/gae.log',
filemode='a',
level=logging.DEBUG,
format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s')
#...

Restart the dev_appserver.py after changed the dev_appserver_main.py.

Now, the log console would not show any log messages, instead all output to a file (In this example, all log messages will be output to “/Users/lokjack/gae.log“).

Note
This hacks only works on local GAE development environment.

Download Source Code

Download it – gae-logging-to-file.zip (11 kb)

How to install Google Plugin for Eclipse

In this tutorials, we will show you how to install “Google Plugin for Eclipse“, in Eclipse 3.7 (Indigo).

1. Installation

In Eclipse 3.7, click “Help” –> “Install New Software…“, copy and paste following URL :

http://dl.google.com/eclipse/plugin/3.7

Note
For other Eclipse version like 3.3, 3.4, 3.5, 3.6, please refer to this GAE Eclipse documentation.

Figure : Select “Google Plugin for Eclipse (required), and Google App Engine SDK for Java.

install google plugin for eclipse 3.7 install google plugin for eclipse 3.7
Note
The Google App Engine Java SDK is optional, you can download it together with the plugin, Or, download manually from GAE website and link it to “Google Plugin for Eclipse” later.

2. Verification

Wait few minutes for the installation progress, when done, Eclipse prompts you to restart, click yes and the Google Plugin for Eclipse is installed.

Figure – A small Google icon is available in the Eclipse toolbar.

instal googe plugin for eclipse 3.7 success instal googe plugin for eclipse 3.7 success
Don’t use JRE 7
At this moment, don’t use JRE 7 runtime in Eclipse, it will caused the “Google Plugin for Eclipse” failed to install, and prompts you many “jar has been tampered” error message. See this thread.

javax.swing.tree.TreeNode is a restricted class

Problem

Developing Struts2 on Google App Engine, in following environment.

  1. Struts 2.3.1.2
  2. freemaker 2.3.18
  3. JDK 1.6
  4. Eclipse 3.7 + Google Plugin for Eclipse
  5. Google App Engine Java SDK 1.6.3.1

GAE complaints that javax.swing.tree.TreeNode is a restricted class in local development, if deployed on real GAE production environment, error message is gone.

Caused by:
java.lang.NoClassDefFoundError: javax.swing.tree.TreeNode is a restricted class.
Please see the Google App Engine developer's guide for more details.
at com.google.appengine.tools.development.agent.runtime.Runtime.reject(Runtime.java:51)
at freemarker.core.TextBlock.isIgnorable(TextBlock.java:375)
at freemarker.core.TextBlock.heedsTrailingWhitespace(TextBlock.java:337)

Solution

No idea why it works in GAE production environment, for local GAE environment, you can overload the TextBlock class, compile below code and move it to WEB-INF/classes, so that it will overload the original TextBlock class.

treenote is restricted class treenote is restricted class

File : TextBlock.java

/* * Copyright (c) 2003 The Visigoth Software Society. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowledgement: * "This product includes software developed by the * Visigoth Software Society (http://www.visigoths.org/)." * Alternately, this acknowledgement may appear in the software itself, * if and wherever such third-party acknowledgements normally appear. * * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the * project contributors may be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact visigo...@visigoths.org. * * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth" * nor may "FreeMarker" or "Visigoth" appear in their names * without prior written permission of the Visigoth Software Society. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Visigoth Software Society. For more * information on the Visigoth Software Society, please see * http://www.visigoths.org/ */
package freemarker.core;
 
import java.io.IOException;
 
/** * A TemplateElement representing a block of plain text. * * @version $Id: TextBlock.java,v 1.17 2004/01/06 17:06:42 szegedia Exp $ */
public final class TextBlock extends TemplateElement {
private static final char[] EMPTY_CHAR_ARRAY = new char[0];
static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
// We're using char[] instead of String for storing the text block because
// Writer.write(String) involves copying the String contents to a char[]
// using String.getChars(), and then calling Writer.write(char[]). By
// using Writer.write(char[]) directly, we avoid array copying on each
// write.
private char[] text;
private final boolean unparsed;
 
public TextBlock(String text) {
this(text, false);
}
 
public TextBlock(String text, boolean unparsed) {
this(text.toCharArray(), unparsed);
}
 
private TextBlock(char[] text, boolean unparsed) {
this.text = text;
this.unparsed = unparsed;
}
 
/** * Simply outputs the text. */
public void accept(Environment env) throws IOException {
env.getOut().write(text);
}
 
public String getCanonicalForm() {
String text = new String(this.text);
if (unparsed) {
return "<#noparse>" + text + "</#noparse>";
}
return text;
}
 
public String getDescription() {
String s = new String(text).trim();
if (s.length() == 0) {
return "whitespace";
}
if (s.length() > 20) {
s = s.substring(0, 20) + "...";
s = s.replace('\n', ' ');
s = s.replace('\r', ' ');
}
return "text block (" + s + ")";
}
 
TemplateElement postParseCleanup(boolean stripWhitespace) {
if (text.length == 0)
return this;
int openingCharsToStrip = 0, trailingCharsToStrip = 0;
boolean deliberateLeftTrim = deliberateLeftTrim();
boolean deliberateRightTrim = deliberateRightTrim();
if (!stripWhitespace || text.length == 0) {
return this;
}
if (parent.parent == null && previousSibling() == null)
return this;
if (!deliberateLeftTrim) {
trailingCharsToStrip = trailingCharsToStrip();
}
if (!deliberateRightTrim) {
openingCharsToStrip = openingCharsToStrip();
}
if (openingCharsToStrip == 0 && trailingCharsToStrip == 0) {
return this;
}
this.text = substring(text, openingCharsToStrip, text.length
- trailingCharsToStrip);
if (openingCharsToStrip > 0) {
this.beginLine++;
this.beginColumn = 1;
}
if (trailingCharsToStrip > 0) {
this.endColumn = 0;
}
return this;
}
 
/** * Scans forward the nodes on the same line to see whether there is a * deliberate left trim in effect. Returns true if the left trim was * present. */
private boolean deliberateLeftTrim() {
boolean result = false;
for (TemplateElement elem = this.nextTerminalNode(); elem != null
&& elem.beginLine == this.endLine; elem = elem
.nextTerminalNode()) {
if (elem instanceof TrimInstruction) {
TrimInstruction ti = (TrimInstruction) elem;
if (!ti.left && !ti.right) {
result = true;
}
if (ti.left) {
result = true;
int lastNewLineIndex = lastNewLineIndex();
if (lastNewLineIndex >= 0 || beginColumn == 1) {
char[] firstPart = substring(text, 0,
lastNewLineIndex + 1);
char[] lastLine = substring(text, 1 + lastNewLineIndex);
if (trim(lastLine).length == 0) {
this.text = firstPart;
this.endColumn = 0;
} else {
int i = 0;
while (Character.isWhitespace(lastLine[i])) {
i++;
}
char[] printablePart = substring(lastLine, i);
this.text = concat(firstPart, printablePart);
}
}
}
}
}
if (result) {
}
return result;
}
 
/** * Checks for the presence of a t or rt directive on the same line. Returns * true if the right trim directive was present. */
private boolean deliberateRightTrim() {
boolean result = false;
for (TemplateElement elem = this.prevTerminalNode(); elem != null
&& elem.endLine == this.beginLine; elem = elem
.prevTerminalNode()) {
if (elem instanceof TrimInstruction) {
TrimInstruction ti = (TrimInstruction) elem;
if (!ti.left && !ti.right) {
result = true;
}
if (ti.right) {
result = true;
int firstLineIndex = firstNewLineIndex() + 1;
if (firstLineIndex == 0) {
return false;
}
if (text.length > firstLineIndex
&& text[firstLineIndex - 1] == '\r'
&& text[firstLineIndex] == '\n') {
firstLineIndex++;
}
char[] trailingPart = substring(text, firstLineIndex);
char[] openingPart = substring(text, 0, firstLineIndex);
if (trim(openingPart).length == 0) {
this.text = trailingPart;
this.beginLine++;
this.beginColumn = 1;
} else {
int lastNonWS = openingPart.length - 1;
while (Character.isWhitespace(text[lastNonWS])) {
lastNonWS--;
}
char[] printablePart = substring(text, 0, lastNonWS + 1);
if (trim(trailingPart).length == 0) {
// THIS BLOCK IS HEINOUS! THERE MUST BE A BETTER
// WAY! REVISIT (JR)
boolean trimTrailingPart = true;
for (TemplateElement te = this.nextTerminalNode(); te != null
&& te.beginLine == this.endLine; te = te
.nextTerminalNode()) {
if (te.heedsOpeningWhitespace()) {
trimTrailingPart = false;
}
if (te instanceof TrimInstruction
&& ((TrimInstruction) te).left) {
trimTrailingPart = true;
break;
}
}
if (trimTrailingPart)
trailingPart = EMPTY_CHAR_ARRAY;
}
this.text = concat(printablePart, trailingPart);
}
}
}
}
return result;
}
 
/* * private String leftTrim(String s) { int i =0; while (i<s.length()) { if * (!Character.isWhitespace(s.charAt(i))) break; ++i; } return * s.substring(i); } */
private int firstNewLineIndex() {
String content = new String(text);
int newlineIndex1 = content.indexOf('\n');
int newlineIndex2 = content.indexOf('\r');
int result = newlineIndex1 >= 0 ? newlineIndex1 : newlineIndex2;
if (newlineIndex1 >= 0 && newlineIndex2 >= 0) {
result = Math.min(newlineIndex1, newlineIndex2);
}
return result;
}
 
private int lastNewLineIndex() {
String content = new String(text);
return Math.max(content.lastIndexOf('\r'), content.lastIndexOf('\n'));
}
 
/** * figures out how many opening whitespace characters to strip in the * post-parse cleanup phase. */
private int openingCharsToStrip() {
int newlineIndex = firstNewLineIndex();
if (newlineIndex == -1 && beginColumn != 1) {
return 0;
}
++newlineIndex;
if (text.length > newlineIndex) {
if (newlineIndex > 0 && text[newlineIndex - 1] == '\r'
&& text[newlineIndex] == '\n') {
++newlineIndex;
}
}
if (new String(text).substring(0, newlineIndex).trim().length() > 0) {
return 0;
}
// We look at the preceding elements on the line to see if we should
// strip the opening newline and any whitespace preceding it.
for (TemplateElement elem = this.prevTerminalNode(); elem != null
&& elem.endLine == this.beginLine; elem = elem
.prevTerminalNode()) {
if (elem.heedsOpeningWhitespace()) {
return 0;
}
}
return newlineIndex;
}
 
/** * figures out how many trailing whitespace characters to strip in the * post-parse cleanup phase. */
private int trailingCharsToStrip() {
String content = new String(text);
int lastNewlineIndex = lastNewLineIndex();
if (lastNewlineIndex == -1 && beginColumn != 1) {
return 0;
}
String substring = content.substring(lastNewlineIndex +1);
if (substring.trim().length() >0) {
return 0;
}
// We look at the elements afterward on the same line to see if we should
// strip any whitespace after the last newline
for (TemplateElement elem = this.nextTerminalNode();
elem != null && elem.beginLine == this.endLine;
elem = elem.nextTerminalNode())
{
if (elem.heedsTrailingWhitespace())
{
return 0;
}
}
return substring.length();
}
boolean heedsTrailingWhitespace() {
if (isIgnorable()) {
return false;
}
for (int i = 0; i < text.length; i++) {
char c = text[i];
if (c == '\n' || c == '\r') {
return false;
}
if (!Character.isWhitespace(c)) {
return true;
}
}
return true;
}
 
boolean heedsOpeningWhitespace() {
if (isIgnorable()) {
return false;
}
for (int i = text.length - 1; i >= 0; i--) {
char c = text[i];
if (c == '\n' || c == '\r') {
return false;
}
if (!Character.isWhitespace(c)) {
return true;
}
}
return true;
}
 
boolean isIgnorable() {
if (text == null || text.length == 0) {
return true;
}
if (!isWhitespace()) {
return false;
}
// do the trick
boolean atTopLevel = true;
TemplateElement prevSibling = previousSibling();
TemplateElement nextSibling = nextSibling();
return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
&& ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
}
 
private boolean nonOutputtingType(TemplateElement element) {
return (element instanceof Macro || element instanceof Assignment
|| element instanceof AssignmentInstruction
|| element instanceof PropertySetting
|| element instanceof LibraryLoad || element instanceof Comment);
}
 
private static char[] substring(char[] c, int from, int to) {
char[] c2 = new char[to - from];
System.arraycopy(c, from, c2, 0, c2.length);
return c2;
}
 
private static char[] substring(char[] c, int from) {
return substring(c, from, c.length);
}
 
private static char[] trim(char[] c) {
if (c.length == 0) {
return c;
}
return new String(c).trim().toCharArray();
}
 
private static char[] concat(char[] c1, char[] c2) {
char[] c = new char[c1.length + c2.length];
System.arraycopy(c1, 0, c, 0, c1.length);
System.arraycopy(c2, 0, c, c1.length, c2.length);
return c;
}
 
boolean isWhitespace() {
return text == null || trim(text).length == 0;
}
}