Category Archive: Java

Подбор окончания в соответствии с количеством

Шлифуя очередную программу и вкладывая в нее души, хочется выводить не: "1 карточек", "2 карточек" и "5 карточек", а "1 карточка", "2 карточки" и 5 "карточек". Т.е. к слову, связанному с целым количественным числительным хочется подобрать правильное окончание. Можно заменить, что для целых количественных числительных максимально можно выделить три различных окончания. Эти рассуждения привели меня к написанию следующей функции:
public static String pickPhrase(int count, String word0,String word1,String word2) {
    int rem = count % 100;
    if(rem < 11 || rem > 14){
        rem = count % 10;
        if(rem == 1) return word1;
        if(rem >= 2 && rem <= 4) return word2;
    } return word0;
}
Функция pickPhrase принимает количество (count) и три вида окончаний (word0, word1, word2). Мысленно можно выделить следующие группы: 0 карточек, 5 карточек, 6 карточек и т.д. 1 карточка, 21 карточка, 31 карточка и т.д. 2 карточки, 3 карточки, 4 карточки и т.д. Для удобства я взял окончания для 0, 1, 2, т.к. у них троих различные окончания. Вам только остается подобрать окончания для 0, 1, 2, а функция по переданную количеству подберет требуемое окончание. word0 - соответствует окончанию для 0-го количества; word1 - соответствует окончанию с количеством в 1 штуку; word2 - соответствует окончанию с количеством в 2 штуки. Например: Пусть дано кол-во карточек от 0 до 100 включительно и нужно к слову "карточка" подобрать окончание в соответствии с количеством. Это делается так:
for (int i = 0; i <= 100; i++) {
    System.out.println(i + " карточ" + pickPhrase(i, "ек", "ка", "ки"));
}
Мы выделили неизменяемую часть слова "карточ" и далее нашей функцией подобрали окончание.
0 карточек
1 карточка
2 карточки
3 карточки
4 карточки
5 карточек
6 карточек
7 карточек
8 карточек
9 карточек
10 карточек
11 карточек
12 карточек
...
96 карточек
97 карточек
98 карточек
99 карточек
100 карточек
Чтобы не выделить неизменяемую часть слова и не допустить ошибку при этом, можно вместо окончаний использовать сами слова. Например:
for (int i = 0; i <= 100; i++) {
    System.out.println(i + " " + pickPhrase(i, "штук", "штука", "штуки"));
}
0 штук
1 штука
2 штуки
3 штуки
4 штуки
5 штук
6 штук
7 штук
8 штук
9 штук
10 штук
11 штук
12 штук
13 штук
...
90 штук
91 штука
92 штуки
93 штуки
94 штуки
95 штук
96 штук
97 штук
98 штук
99 штук
100 штук

Встраивание атрибута Id в тег Body SOAP-запроса

С некоторой версии Java (примерно с 7u25 см. http://stackoverflow.com/questions/17331187/xml-dig-sig-error-after-upgrade-to-java7u25) начались проблемы со встаиванием атрибута Id в тег Body SOAP-запроса. Такое встраивание необходимо с целью подписания запроса ЭЦП. Пока я встретил две ошибки в зависимости каким путем идет встраивание атрибута Id:
  1. com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment
  2. org.apache.xml.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID ...
Чтобы решить проблему нужно специальным образом пометить атрибут Id как ID (простите за тавтологию) Первая ошибка возникает в коде такого вида:
Attr attr = doc.createAttribute("id");
attr.setValue("body");
body.getAttributes().setNamedItem(attr);
Подкорректированная версия:
Attr attr = doc.createAttribute("id");
attr.setValue("body");
body.getAttributes().setNamedItem(attr);
((Element) body).setIdAttributeNode(attr, true);//fix
Вторая ошибка возникает в коде вида:
SOAPBody body = env.getBody();
body.addAttribute(new QName("Id"), id);
Подкорректированная версия:
SOAPBody body = env.getBody();
body.addAttribute(new QName("Id"), id);
body.setIdAttribute("Id", true);//fix
Если помог отпишитесь 🙂

Циклический сдвиг по кругу

Рассмотрим, линейный массив: line По нему мы можем передвигаться вперед и назад, но не выходя за левую и правую границы. Данный массив можно закольцевать. Допустим, у нас есть массив, состоящий из элементов пронумерованных следующим образом: 0 1 2 3 4, мы находимся в позиции с индексом 3 и нам нужно сместиться на три позиции вправо. Наши действия:
  1. Перемещаемся вправо на 4-ю позицию;
  2. Пытаемся переместиться вправо на 5-ю позиции, но ее - нет. Перескакиваем на началом массива в 0-ю позицию;
  3. Перемещаемся вправо на 1-ю позицию. В итоге мы оказались на 1-й позиции.
Теперь рассмотрим обратную ситуацию. Мы в позиции с индекcом 1 и нужно сместиться на три позиции влево:
  1. Перемещаеся влево на 0-ю позицию;
  2. Пытаемся переместиться влево на -1-ю позицию, которой нет, поэтому перемещаемся в конец массива на позицию с индексом 4.
  3. Перемещаемся влево на 3-ю позицию. В итоге оказываемся на 3-й позиции.
circle Реализация смещения вправо:
int[] arr = new int[]{10, 20, 30, 40, 50};
int index = 3;
System.out.println("index = " + index);//Вывод: 3
System.out.println("value = " + arr[index]);//Вывод: 40
int step = 3;
int newIndex = (index + step) % arr.length;
System.out.println("newIndex = " + newIndex);//Вывод: 1
System.out.println("value = " + arr[newIndex]);//Вывод: 20
Реализация смещения влево:
int[] arr = new int[]{10, 20, 30, 40, 50};
int index = 1;
System.out.println("index = " + index);//Вывод: 1
System.out.println("value = " + arr[index]);//Вывод: 20
int step = 13;
int newIndex = (arr.length + (index - step) % arr.length) % arr.length;
System.out.println("newIndex = " + newIndex);//Вывод: 3
System.out.println("value = " + arr[newIndex]);//Вывод: 40
Зацикливание массива реализовано с помощью операции взятия остатка от деления %, это позволяет исключить полностью завершенные циклы.

Подписание SOAP-сообщения KALKAN-апплетом

Стандартный Калкан-апплет не позволяет подписывать SOAP-сообщения. Допустим, перед вами все-таки встала такая или подобная задача... Теоретически можно расширить апплет, добавив свой метод, но для этого нужно убрать родную подпись апплета и установить свою. Важно! Самоподписанный сертификат для подписания апплета не подойдет, нужен легально подписанный сертификат. Дальшейшая практическая инструкция подразумевает, что у вас есть легальный сертификат для подписи апплета, иначе некоторые браузеры могут не принять самоподписанный сертификат.
  1. Создаем maven-проект. Вот содержимое pom.xml:
    
    
        4.0.0
    
        kz.kesh.blog
        kalkan-applet
        1.2.3
    
        
            
                kz.gov.pki
                knca_applet
                0.1.1
            
        
    
        
            knca_applet_patched
            
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    3.2
                    
                        1.7
                        1.7
                        ${project.build.sourceEncoding}
                    
                
                
                    org.apache.maven.plugins
                    maven-shade-plugin
                    2.4.1
                    
                        
                            package
                            
                                shade
                            
                            
                                
                                    
                                        *:*
                                        
                                            META-INF/*.SF
                                            META-INF/*.DSA
                                            META-INF/*.RSA
                                        
                                    
                                
                                
                            
                        
                    
                
                
                    org.apache.maven.plugins
                    maven-jar-plugin
                    2.4
                    
                        
                            false
    
                            
                                false
                                true
                                true
                            
                            
                                *
                                *
                                test@kesh.kz
                                true
                                all-permissions
                                *
                                Kesh blog
                            
                        
                    
                
            
        
    
    
  2. Необходимо добавить в локальный репозиторий maven jar-ку апплета, которую будем прокачивать. Это можно сделать командой:
    mvn install:install-file -Dfile=knca_applet-0.1.1.jar -DgroupId=kz.gov.pki -DartifactId=knca_applet -Dversion=0.1.1 -Dpackaging=jar
    
    У вас должен быть в наличии файл knca_applet-0.1.1.jar и он должен находиться в текущей директории, откуда выполняется вышеуказанная команда.
  3. Далее необходимо создать пакет kz.gov.pki.knca.applet и разместить в нем наш новый класс SuperKalkanApplet:
    package kz.gov.pki.knca.applet;
    
    import kz.gov.pki.kalkan.Storage;
    import kz.gov.pki.kalkan.jce.provider.KalkanProvider;
    import kz.gov.pki.knca.applet.exception.AECodes;
    import kz.gov.pki.knca.applet.exception.AppletException;
    import kz.gov.pki.knca.applet.utils.KeyStoreUtil;
    import org.apache.xml.security.signature.XMLSignature;
    import org.apache.xml.security.transforms.Transforms;
    import org.w3c.dom.Attr;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import org.xml.sax.InputSource;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.transform.OutputKeys;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
    import java.io.FileInputStream;
    import java.io.StringReader;
    import java.io.StringWriter;
    import java.security.AccessController;
    import java.security.KeyStore;
    import java.security.PrivateKey;
    import java.security.PrivilegedAction;
    import java.security.cert.X509Certificate;
    
    public class SuperKalkanApplet extends MainApplet{
    
    	@SuppressWarnings("unused")
    	public ResultWrapper signSoap(final String storageName,final String storagePath,final String alias,final String password,final String xml){
    		return AccessController.doPrivileged(
    				new PrivilegedAction() {
    					public ResultWrapper run() {
    						try {
    							Document doc = xmlToDoc(xml);
    							Node envelope = doc.getFirstChild();
    							Node header = null;
    							Node body = null;
    							for (int i = 0; i < envelope.getChildNodes().getLength(); i++) {
    								Node node = envelope.getChildNodes().item(i);
    								if ("Header".equals(node.getLocalName())) {
    									header = node;
    								} else if ("Body".equals(node.getLocalName())) {
    									body = node;
    								}
    							}
    							if (header == null) {
    								header = doc.createElement(envelope.getPrefix() + ":Header");
    								envelope.insertBefore(header, body);
    							}
    							if (body == null) throw new RuntimeException("Body not found");
    							Attr attr = doc.createAttribute("id");
    							attr.setValue("body");
    							body.getAttributes().setNamedItem(attr);
    							((Element) body).setIdAttributeNode(attr, true);
    
    							Storage storage = getStorage(storageName);
    							KeyStore keyStore = KeyStoreUtil.getKeyStore(storage, storagePath, password.toCharArray(), getProvider());
    
    							PrivateKey key = KeyStoreUtil.getPrivateKey(storage, storagePath, alias, password.toCharArray(), getProvider());
    
    							X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
    							String signMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
    							String digestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
    							if(!"RSA".equals(key.getAlgorithm())){
    								signMethod = "http://www.w3.org/2001/04/xmldsig-more#gost34310-gost34311";
    								digestMethod = "http://www.w3.org/2001/04/xmldsig-more#gost34311";
    							}
    							XMLSignature signature = new XMLSignature(doc, "", signMethod);
    							Transforms transforms = new Transforms(doc);
    							transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
    							transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
    							signature.addDocument("#body", transforms, digestMethod);
    							signature.addKeyInfo(certificate);
    							NodeList nodes = header.getChildNodes();
    							if (nodes.getLength() > 0) {
    								Node node = header.getFirstChild();
    								header.insertBefore(signature.getElement(), node);
    							} else {
    								header.appendChild(signature.getElement());
    							}
    							signature.sign(key);
    							ResultWrapper rw = new ResultWrapper();
    							String signedXml = docToText(doc);
    							System.out.println(signedXml);
    							rw.setResult(signedXml);
    							return rw;
    						} catch (Exception e) {
    							System.err.println("Error: " + e.getMessage());
    							e.printStackTrace();
    							return new ResultWrapper(e.getMessage());
    						}
    					}
    				}
    		);
    	}
    
    	private static Document xmlToDoc(String xmlStr) {
    		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    		factory.setNamespaceAware(true);
    		DocumentBuilder builder;
    		try
    		{
    			builder = factory.newDocumentBuilder();
    			return builder.parse( new InputSource( new StringReader( xmlStr ) ) );
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return null;
    	}
    
    	private static String docToText(Document document) throws Exception {
    		TransformerFactory tf = TransformerFactory.newInstance();
    		Transformer transformer = tf.newTransformer();
    		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
    		StringWriter writer = new StringWriter();
    		transformer.transform(new DOMSource(document),new StreamResult(writer));
    		return writer.getBuffer().toString();
    	}
    
    	private Storage getStorage(String storageName) throws AppletException {
    		Storage storage = Storage.get(storageName);
    		if(storage == null) {
    			System.err.println("Unknown storage name : " + storageName);
    			throw new AppletException(AECodes.UNKNOWN_STORAGE.toString());
    		} else {
    			return storage;
    		}
    	}
    }
    
  4. Теперь необходимо собрать новый jar-файл:
    mvn clean package
    
Сигнатура нового метода следующая
ResultWrapper signSoap(final String storagePath,final String alias,final String password,final String xml)
storagePath - путь к подписи alias - идентификатор сертификата password - пароль xml - SOAP-сообщение для подписи При внедрении апплета в html нужно указать knca_applet_patched.jar (или ваше название) и kz.gov.pki.knca.applet.SuperKalkanApplet (тоже можно изменить).

Реализуем FirstInHashMap на основе HashMap

Однажды мне понадобилась одна вещь, которая работает как стандартный HashMap, но за небольшим исключением. Как известно HashMap хранит пары вида Ключ-Значение (Key-Value), а значение можно извлечь по ключу. Если в HashMap добавляется значение и ключ уже существующий в HashMap, то старое значение заменяется новым. Но мне нужно было, наоборот, чтобы старое значение не затиралось, а новое значение попросту игнорировалось. Для этого пришлось немного расширить HashMap, переопределив метод put:
public class FirstInHashMap extends HashMap{
	@Override
	public V put(K key, V value) {
		return super.containsKey(key) ? super.get(key) : super.put(key,value);
	}
}
Здесь первым делом идет проверка на присутствие ключа в HashMap. При наличии ключа идет поиск значения. При отсутствии ключа ключ и значение добавляются в HashMap. Заметим, что реализация нашего метода построена на вызовах методом (containsKey, get и put) из базового класса HashMap. Сигнатура метода put требует возвращения значения, значение берется от методов get и put соответственно. Сравним работу HashMap и нашего FirstInHashMap:
Map map = new HashMap<>();
map.put("word",1);
map.put("word",5);

Map myMap = new FirstInHashMap<>();
myMap.put("word",1);
myMap.put("word",5);

System.out.println("HashMap: " + map.get("word"));//HashMap вернет 5
System.out.println("FirstInHashMap: " + myMap.get("word"));//FirstInHashMap 1
В данном примере мы кладем по порядку значения 1 и 5 с ключом word в обе Map'ы. HashMap вернет 5, а FirstInHashMap вернет 1.

Разбивка текста на блоки фиксированной длины

Допустим, перед нами стоит задача разбить текст на блоки фиксированной длины. Как пример можно взять строку:
oxxxxooox
и разбить на блоки по три символа:
oxx
xxo
oox
Это можно сделать следующим кодом:
String text = "oxxxxooox";//исходный текст
int portion = 3;//размер блока
int i = 0;
while(i < text.length()){
	System.out.println(text.substring(i,Math.min(i += portion, text.length())));
}
Функция substring вырезает часть строки в диапазоне [beginIndex,endIndex), т.е. начиная с позиции beginIndex включительно и до позиции endIndex (не включая саму позицию endIndex). i += portion - определяет конец текущего блока и одновременно начало следующего. Math.min(i += portion, text.length()) - позволяет определить конец блока и не выйти за пределы самого текста.

Дополнение числа ведущими нулями на Java

Всегда хотелось найти быстрый и короткий способ дополнить число ведущими нулями. Например, часто возникает необходимость вывести последовательность 1,2,3,...,10 в таком виде: 01,02,03,...,10. И сегодня я нашел здесь такой способ. Рассмотрим, этот способ на примере следующей задачи: выведем все числа от 1 до 100, дополнив числа до длины равной трем, т.е. 001,002,003,...,099,100.
public class LeadingZero {
	public static void main(String[] args) {
		for (int i = 1; i <= 100; i++) {
			System.out.println(zero("" + i));
		}
	}
	static String zero(String value){
		return ("000" + value).substring(value.length());
	}
}
"000" - это шаблон, который мы присоединяем спереди к цифрам числа, но т.к. шаблон уже содержим максимальное кол-во символов, то мы должны проигнорировать первые нули в кол-ве равному избытку, т.е. кол-ву символов в исходном числе, и тогда мы получим искомое число с ведущими нулями заданной длины. Со своей задачей прекрасно справляется функция substring, которая берет часть строки от заданной позиции и до конца строки. leading_zero_illustration Также необходимо учитывать, что число может выйти за пределы шаблона. Вывод программы будет следующим:
001
002
003
...
009
010
011
...
098
099
100

Набор методов для маршалинга и анмаршалинга

При создании и обращении к веб-сервисам в большинстве случаем мы можем забыть и не знать о маршалинге и анмаршалинге (демаршалинге). Но бывают интеграции, где каким-то текстовым полем передается XML-ка. Вот тут нужно с одной стороны уметь делать маршалинг, а с другой анмаршалинг. Предлагаю набор методов для проведения данных операций:
import javax.xml.bind.*;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;

/**
 * Набор методов для маршалинга и анмаршалинга
 */
public class XmlUtils {

	/**
	 * Маршалинг в строку
	 * @param t объект
	 * @param  тип объекта
	 * @return XML в виде строки
	 * @throws JAXBException
	 */
	public static String marshalRoot(T t) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(t.getClass());
		Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
		StringWriter sw = new StringWriter();
		marshaller.marshal(t,sw);
		return sw.toString();
	}

	/**
	 * Маршалинг в файл
	 * @param t объект
	 * @param file файл
	 */
	public static void marshalRoot(T t,File file){
		marshal(t,file,false);
	}

	/**
	 * Маршалинг в файл с возможностью вывода в консоль
	 * @param t объект
	 * @param file файл
	 * @param isAddToLog делать вывод в консоль или нет
	 */
	public static void marshal(T t,File file,boolean isAddToLog){
		String filePath = file.getAbsolutePath();
		try {
			JAXBContext jaxbContext = JAXBContext.newInstance(t.getClass());
			Marshaller marshaller = jaxbContext.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);
			marshaller.marshal(t,file);
			if(isAddToLog){
				marshaller.marshal(t,System.out);
			}
		} catch (JAXBException e) {
			System.out.println("Не удалось сбросить xml в файл: " + filePath);
			e.printStackTrace();
		}
		System.out.println("Xml saved to: " + filePath);
	}

	/**
	 * Анмаршалинг (Демаршалинг) XML в объект
	 * @param xml входной XML
	 * @param _class класс объекта, который ходит получить
	 * @return объект
	 * @throws JAXBException
	 */
	@SuppressWarnings("unchecked")
	public static  T unmarshall(String xml,Class _class) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(_class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		JAXBElement element = (JAXBElement)unmarshaller.unmarshal(new StringReader(xml));
		return element.getValue();
	}

	/**
	 * Анмаршалинг XML root-элемента (@XmlRootElement) в объект
	 * @param xml входной XML
	 * @param _class класс объекта, который ходит получить
	 * @return объект
	 * @throws JAXBException
	 */
	@SuppressWarnings("unchecked")
	public static  T unmarshallRoot(String xml,Class _class) throws JAXBException{
		JAXBContext context = JAXBContext.newInstance(_class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		return (T)unmarshaller.unmarshal(new StringReader(xml));
	}


}

Удаленный дебаг в IntelliJ IDEA (на примере JBoss EAP)

Использование локальной или удаленной отладки может очень помочь и сэкономить время. По умолчанию удаленная отладка в JBoss отключена, для ее активации нужно внести изменения в конфигурационный файл %EAP_HOME%/bin/standalone.conf.
  1. Найдите и раскомментируйте следующую строку:
    JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n"
    
    Видно, что мы задействовали порт 8787.
  2. Перегрузите JBoss
  3. Осталось настроить IDEA Зайдите в меню Run и выберите пункт Edit configurations...: remote_debug_1 В открывшемся окне Run/Debug configurations выберите пункт Remote: remote_debug_2 Придумайте название для данной конфигурации (поле Name), пропишите адрес(Host) и порт(Port) JBoss'а, на котором включена удаленная отладка. Нажмите OK: remote_debug_3 В качестве адреса можно указать localhost.
  4. Теперь попробуем подключаться к работающему JBoss'у, для этого выберите в списке созданную конфигурацию и нажмите "зеленого жучка", как на картинке: remote_debug_4 При успешном подключении в консоли IDEA вы увидите следующее сообщение:
    Connected to the target VM, address: '10.1.0.50:8787', transport: 'socket'
    
  5. Далее мы можем расставить необходимые breakpoint'ы, где необходимо приостановить код и просмотреть содержимое переменных: remote_debug_5 Вот так в IntelliJ IDEA выглядит отображение содержимого переменных - это очень удобно: remote_debug_6
  6. Для навигации по исполняемому коду вам помогут следующие кнопки: remote_debug_9 Одна из самых полезных - Step Over(перешагнуть), вызывается нажатием функциональной клавиши F8.
  7. Также IntelliJ IDEA предоставляет некоторые возможности по горячей замене классов (Hot Swap). Например, вы можете что-то изменить внутри метода, нажать на кнопку Make Project, и все изменные классы будут перегружены: remote_debug_7 Если изменения несовместимы с горячей заменой, то вам будет сообщено о невозможности замены классов.
  8. И последнее - для выхода из режима отладки нажмите кнопку Stop: remote_debug_8 В консоли появиться сообщение об отсоединении от сервера:
    Disconnected from the target VM, address: '10.1.0.50:8787', transport: 'socket
    

Правильная разбивка строки функцией split (Java)

Часто при парсинге данных мы разбиваем строку по разделителю. Один из способов - это использование функции split(regex), где regex - это регулярное выражение. Разобъем следующую строку на части:
Java World|0123456|false
Очень часто разделителем выступает вертикальная черта |, т.к. в функция split принимает регулярное выражение, а символ | (pipe) используется в регулярных выражениях для задания условия "или", то этот символ нужно экранировать. Разбить строку можно следующим кодом:
"Java World|0123456|false".split("\\|")
На первый взгляд кажется, что все будет работать без проблем. Возможно, это будет и так, но допустим вам попадется строка, в которой разделитель стоит в конце строки:
Java World|0123456|
Если разбить эту строку:
"Java World|0123456|".split("\\|")
Вы в своем коде можете ожидать массив из трех элементов: "Java World", "0123456", "". Но массив будет содержать два элемента: "Java World", "0123456", потому что разделитель, стоящий в конце строки игнорируется. Для того, чтобы последний разделитель не игнорировался нужно в функцию split, передать вторым параметром -1. У split есть еще одна сигнатура вида: split(regex,limit), где параметр limit означает максимальное кол-во элементов в итоговом массиве. Примеры использования:
"Java World|0123456|".split("\\|",1)
//вернет одну строку, игнорируя разделители: "Java World|0123456|"
"Java World|0123456|".split("\\|",2)
//вернет две строки, игнорируя все разделители, кроме первого: "Java World", "0123456|"
"Java World|0123456|".split("\\|",5)
//вернет три строки - максимально возможное разбиение строки,
//хотя мы пытались разбить строку на 5 частей: "Java World", "0123456", ""
На самом деле метод split(regex) вызывает split(regex,0), т.е. limit равен 0, и функция ведет себя так, как описано в начале статьи. Если передать параметр limit, равный -1 (или любое отрицательное число), то разделитель в конце строки не игнорируется:
"Java World|0123456|".split("\\|",-1)
//вернет три строки: "Java World", "0123456" и ""
Заключение: На практике очень часто приходится пользоваться split(regex), но не нужно забывать и про split(regex,-1). Что касается использования split(regex,limit) при limit > 0, то я не представляю, где это может пригодиться, и, думаю, используется это очень редко.