Author Archive

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

Рассмотрим, линейный массив: 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));
	}


}

Разрезка аудио по тишине с помощью Audacity

У меня был несжатый звуковой файл в формате wav, в котором были записаны слова и словосочетания, озвученные диктором. Между словами и словосочетаниями была выдержана пауза, которая была больше паузы между словами в словосочетаниях. Слов было порядка тысячи. И в итоге мне нужно было получить тысячу сжатых файлов в формате mp3. Программа Audacity выполнила эту задачу очень хорошо.
  1. Откройте звуковой файл с помощью программы Audacity: 01 На рисунке видны ярко выраженные паузы.
  2. Теперь выделим все фрагменты аудиозаписи, разделенные тишиной. Для этого в меню Анализ выберите пункт Sound Finder ... 02 анализ 03 sound finder
  3. Далее появиться окно настройки параметров. По умолчанию минимальная продолжительность тишины между звуками - 1 секунда. В моем же случае понадобилось уменьшить этот параметр до 0,4 секунды, т.е. в этом окне можно поиграться с параметрами: 04 duration
  4. После указания параметров и нажатия кнопки OK, все искомые фрагменты будут выделены: 05 selection
  5. Следующая задача - это экспорт этих фрагментов в mp3-файлы, делается это через меню Файл: 06 file Выберите в нем пункт Экспортировать в несколько файлов...: 07 export
  6. Выберите формат экспорта mp3 (или любой доступный), укажите каталог экспорта и нажмите Экспорт: 08 export dir
  7. Далее нужно будет заполнить метаданные, которые являются необязательными, для каждого файла: 09 meta К сожалению, у меня не получилось сделать эту операцию скопом 😕 p.s. Я не поленился и нажал кнопку ОК для каждого файла, а их было около 1000. Зато в итоге я получил качественно разбитые записи.

Удаленный дебаг в 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
    

Поиск по содержимому файлов в Linux

Редко пользуюсь поиском по содержимому файлов, но иногда метко как он нужен... Варианты использования Поиск номера телефона во всех xml-файлах
grep 77051810084 *.xml
Поиск слова "kesh" во всех файлах
grep kesh *.*
Поиск словосочетания
grep "Hello World" *.*
Поиск по регулярному выражению
grep -e "^k.*esh$" *.*

Правильная разбивка строки функцией 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, то я не представляю, где это может пригодиться, и, думаю, используется это очень редко.