tag:blogger.com,1999:blog-74073962077583837242024-02-22T08:50:25.547+03:00Заметки программистераIT - это прекрасно!Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.comBlogger68125tag:blogger.com,1999:blog-7407396207758383724.post-52231719069026573212020-04-17T10:42:00.068+03:002021-05-09T18:04:07.011+03:00Vim Cheat Sheet<div dir="ltr" style="text-align: left;" trbidi="on">
<table>
<tbody>
<tr valign="top">
<td>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt_lqA6mB3hqUIdu0NyayQDnAjreo2JTG-knbLdfb_qPQJXVMUm6wuCu599bIaMtQW2lqFEFII3e8gljJNBd6UKOM-df0SfhyphenhyphenEIzrYxgw9KIX64SGC_HzEWqyyHo9zuIdizPGZKCxKAsil/s1600/icon-96x96.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="96" data-original-width="96" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt_lqA6mB3hqUIdu0NyayQDnAjreo2JTG-knbLdfb_qPQJXVMUm6wuCu599bIaMtQW2lqFEFII3e8gljJNBd6UKOM-df0SfhyphenhyphenEIzrYxgw9KIX64SGC_HzEWqyyHo9zuIdizPGZKCxKAsil/s1600/icon-96x96.png" /></a>
</div>
</td>
<td>This is my fork of the <a href="https://vim.rtorr.com/">https://vim.rtorr.com/</a>.</td>
</tr>
</tbody>
</table>
<a name='more'></a>
<main></main><br />
<div class="container">
<div class="commands-container">
<!--Global-->
<div class="grid-block">
<div class="grid-lg-1-3">
<h2>
Global</h2>
<ul>
<li>
<kbd>:help keyword</kbd> - open help for keyword
</li>
<li>
<kbd>:e file</kbd> - open file
</li>
<li>
<kbd>:e dir</kbd> - open file manager
</li>
<li>
<kbd>:so file</kbd> - execute file in the vim
</li>
<li>
<kbd>:r filename</kbd> - Insert content of the file filename below the cursor
</li>
<li>
<kbd>:r !command</kbd> - Execute command and insert its output below the cursor
</li>
<li>
<kbd>:pwd</kbd> - print current working directory
</li>
<li>
<kbd>:cd %:p:h</kbd> - change to the directory of the currently open file (this sets the current directory for all windows in Vim)
</li>
<li>
<kbd>:cd</kbd> - change current working directory
</li>
<li>
<kbd>:! command</kbd> - invoke external command
</li>
<li>
<kbd>:syntax on/off</kbd> - turn on/off syntax highlighting
</li>
<li>
<kbd>K</kbd> - open man page for word under the cursor
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Undo/redo
</h2>
<ul>
<li>
<kbd>u</kbd> - undo the last operation
</li>
<li>
<kbd>Ctrl</kbd>+<kbd>r</kbd> - redo the last undo
</li>
</ul>
<h2>
Save and Exiting</h2>
<ul>
<li>
<kbd>:w</kbd> - write (save) the file, but don't exit
</li>
<li>
<kbd>:wa</kbd> - write (save) all files, but don't exit
</li>
<li>
<kbd>:w !sudo tee %</kbd> - write out the current file using sudo
</li>
<li>
<kbd>:wq</kbd> or <kbd>:x</kbd> or <kbd>ZZ</kbd> - write (save) and quit
</li>
<li>
<kbd>:saveas file</kbd> - save file as
</li>
<li>
<kbd>:q</kbd> - quit (fails if there are unsaved changes)
</li>
<li>
<kbd>:q!</kbd> or <kbd>ZQ</kbd> - quit and throw away unsaved changes
</li>
<li>
<kbd>:wqa</kbd> - write (save) and quit on all tabs
</li>
</ul>
<div class="well">
<strong>Tip:</strong> Symblos to fast navigation in the Ex mode<p/>
<table>
<tr>
<td><kbd>%</kbd></td><td>The entire file (shorthand for :1,$)</td>
</tr>
<tr>
<td><kbd>0</kbd></td><td>Virtual line above first line of the file</td>
</tr>
<tr>
<td><kbd>1</kbd></td><td>First line of the file</td>
</tr>
<tr>
<td><kbd>.</kbd></td><td>Line where the cursor is placed</td>
</tr>
<tr>
<td><kbd>$</kbd></td><td>Last line of the file</td>
</tr>
<tr>
<td><kbd>'m</kbd></td><td>Line containing mark m</td>
</tr>
<tr>
<td><kbd>'<</kbd></td><td>Start of visual selection</td>
</tr>
<tr>
<td><kbd>'></kbd></td><td>End of visual selection</td>
</tr>
</table>
</div>
</div>
<div class="grid-lg-1-3">
<h2>Command line</h2>
<ul>
<li>
<kbd>q:</kbd> show commands history
</li>
<li>
<kbd>:</kbd><kbd>Ctrl</kbd>+<kbd>r</kbd> <kbd>Ctrl</kbd>+<kbd>w</kbd> - insert current word into the command line
</li>
<li>
<kbd>:</kbd><kbd>Ctrl</kbd>+<kbd>r</kbd> <kbd>"</kbd> - paste from “ register
</li>
<li>
<kbd>:[range]d[elete] [x]</kbd> - delete specified lines [into register x]
</li>
<li>
<kbd>:[range]y[ank] [x]</kbd> - yank specified lines [into register x]
</li>
<li>
<kbd>:[line]p[ut] [x]</kbd> - put the text from register x after the specified line
</li>
<li>
<kbd>:[range]copy {address}</kbd> - copy the specified lines to below the line specified by {address}
</li>
<li>
<kbd>:[range]m[ove] {address}</kbd> - move the specified lines to below the line specified by {address}
</li>
<li>
<kbd>:[range]normal {commands}</kbd> - execute Normal mode {commands} on each specified line
</li>
<li>
<kbd>:[r]s[ubstitute]/{p}/{s}/[f]</kbd> - substitute command allows us to find and replace one chunk of text with another
</li>
<li>
<kbd>:[r] g[lobal][!] /{p}/ [cmd]</kbd> - allows to run an Ex command on each line that matches a particular pattern
</li>
</ul>
</div>
</div>
<div class="container"></div>
<!--Editing-->
<div class="grid-block">
<div class="grid-lg-1-3">
<h2>
Editing</h2>
<ul>
<li>
<kbd>r</kbd> - replace a single character
</li>
<li>
<kbd>J</kbd> - join line below to the current one with one space in between
</li>
<li>
<kbd>gJ</kbd> - join line below to the current one without space in between
</li>
<li>
<kbd>gwip</kbd> - reflow paragraph
</li>
<li>
<kbd>cc</kbd> - change (replace) entire line
</li>
<li>
<kbd>C</kbd> - change (replace) to the end of the line
</li>
<li>
<kbd>c$</kbd> - change (replace) to the end of the line
</li>
<li>
<kbd>ciw</kbd> - change (replace) entire word
</li>
<li>
<kbd>cw</kbd> - change (replace) to the end of the word
</li>
<li>
<kbd>s</kbd> - delete character and substitute text
</li>
<li>
<kbd>S</kbd> - delete line and substitute text (same as cc)
</li>
<li>
<kbd>xp</kbd> - transpose two letters (delete and paste)
</li>
<li>
<kbd>.</kbd> - repeat last command
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Cut and paste</h2>
<ul>
<li>
<kbd>yy</kbd> - yank (copy) a line
</li>
<li>
<kbd>2yy</kbd> - yank (copy) 2 lines
</li>
<li>
<kbd>yw</kbd> - yank (copy) the characters of the word from the cursor position to the start of the next
word
</li>
<li>
<kbd>y$</kbd> - yank (copy) to end of line
</li>
<li>
<kbd>p</kbd> - put (paste) the clipboard after cursor
</li>
<li>
<kbd>P</kbd> - put (paste) before cursor
</li>
<li>
<kbd>dd</kbd> - delete (cut) a line
</li>
<li>
<kbd>2dd</kbd> - delete (cut) 2 lines
</li>
<li>
<kbd>dw</kbd> - delete (cut) the characters of the word from the cursor position to the start of the next
word
</li>
<li>
<kbd>D</kbd> - delete (cut) to the end of the line
</li>
<li>
<kbd>d$</kbd> - delete (cut) to the end of the line
</li>
<li>
<kbd>x</kbd> - delete (cut) character
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Registers</h2>
<ul>
<li>
<kbd>:reg</kbd> - show registers content
</li>
<li>
<kbd>"xy</kbd> - yank into register x
</li>
<li>
<kbd>"+y</kbd> - yank into system clipboard
</li>
<li>
<kbd>"xp</kbd> - paste contents of register x
</li>
</ul>
<div class="well">
<strong>Tip</strong> Registers are being stored in ~/.viminfo, and will be loaded again on next restart of
vim.
</div>
<div class="well">
<strong>Tip</strong> Register 0 contains always the value of the last yank command.
</div>
<ul>
<li>
<kbd>:cn</kbd> - jump to the next match
</li>
<li>
<kbd>:cp</kbd> - jump to the previous match
</li>
<li>
<kbd>:copen</kbd> - open a window containing the list of matches
</li>
</ul>
</div>
</div>
</div>
<!--Visual mode-->
<div class="grid-block">
<div class="grid-lg-1-3">
<h2>
Insert mode - inserting/appending text</h2>
<ul>
<li>
<kbd>i</kbd> - insert before the cursor
</li>
<li>
<kbd>I</kbd> - insert at the beginning of the line
</li>
<li>
<kbd>a</kbd> - insert (append) after the cursor
</li>
<li>
<kbd>A</kbd> - insert (append) at the end of the line
</li>
<li>
<kbd>o</kbd> - append (open) a new line below the current line
</li>
<li>
<kbd>O</kbd> - append (open) a new line above the current line
</li>
<li>
<kbd>ea</kbd> - insert (append) at the end of the word
</li>
<li>
<kbd>Esc</kbd> or <kbd>Ctrl</kbd> + <kbd>[</kbd> - exit insert mode
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>o</kbd> - invoke one command in the Normal mode
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>rx</kbd> - paste contents of register x
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Marking text (visual mode)</h2>
<ul>
<li>
<kbd>v</kbd> - start visual mode, mark lines, then do a command (like y-yank)
</li>
<li>
<kbd>V</kbd> - start linewise visual mode
</li>
<li>
<kbd>o</kbd> - move to other end of marked area
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>v</kbd> - start visual block mode
</li>
<li>
<kbd>O</kbd> - move to other corner of block
</li>
<li>
<kbd>aw</kbd> - mark a word
</li>
<li>
<kbd>ab</kbd> - a block with ()
</li>
<li>
<kbd>aB</kbd> - a block with {}
</li>
<li>
<kbd>ib</kbd> - inner block with ()
</li>
<li>
<kbd>iB</kbd> - inner block with {}
</li>
<li>
<kbd>Esc</kbd> - exit visual mode
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Visual commands</h2>
<ul>
<li>
<kbd>></kbd> - shift text right
</li>
<li>
<kbd><</kbd> - shift text left
</li>
<li>
<kbd>y</kbd> - yank (copy) marked text
</li>
<li>
<kbd>d</kbd> - delete marked text
</li>
<li>
<kbd>~</kbd> - switch case
</li>
</ul>
<h2>
Macros</h2>
<ul>
<li>
<kbd>qa</kbd> - record macro a
</li>
<li>
<kbd>q</kbd> - stop recording macro
</li>
<li>
<kbd>@a</kbd> - run macro a
</li>
<li>
<kbd>@@</kbd> - rerun last run macro
</li>
</ul>
</div>
</div>
<!--Navigation-->
<div class="grid-block">
<div class="grid-lg-1-3">
<h2>
Cursor movement around the text</h2>
<ul>
<li>
<kbd>w</kbd> - jump forwards to the start of a word
</li>
<li>
<kbd>W</kbd> - jump forwards to the start of a word (words can contain punctuation)
</li>
<li>
<kbd>e</kbd> - jump forwards to the end of a word
</li>
<li>
<kbd>E</kbd> - jump forwards to the end of a word (words can contain punctuation)
</li>
<li>
<kbd>b</kbd> - jump backwards to the start of a word
</li>
<li>
<kbd>B</kbd> - jump backwards to the start of a word (words can contain punctuation)
</li>
<li>
<kbd>0</kbd> - jump to the start of the line
</li>
<li>
<kbd>^</kbd> - jump to the first non-blank character of the line
</li>
<li>
<kbd>$</kbd> - jump to the end of the line
</li>
<li>
<kbd>g_</kbd> - jump to the last non-blank character of the line
</li>
<li>
<kbd>gg</kbd> - go to the first line of the document
</li>
<li>
<kbd>G</kbd> - go to the last line of the document
</li>
<li>
<kbd>5G</kbd> - go to line 5
</li>
<li>
<kbd>fx</kbd> - jump to next occurrence of character x
</li>
<li>
<kbd>tx</kbd> - jump to before next occurrence of character x
</li>
<li>
<kbd>Fx</kbd> - jump to previous occurence of character x
</li>
<li>
<kbd>Tx</kbd> - jump to after previous occurence of character x
</li>
<li>
<kbd>;</kbd> - repeat previous f, t, F or T movement
</li>
<li>
<kbd>,</kbd> - repeat previous f, t, F or T movement, backwards
</li>
<li>
<kbd>*</kbd> - jumps to the next such word
</li>
</ul>
<div class="well">
<strong>Tip</strong>
Prefix a cursor movement command with a number to repeat it. For example, <kbd>4j</kbd> moves down 4
lines.
</div>
</div>
<div class="grid-lg-1-3">
<ul>
<li></li>
<li>
<kbd>%</kbd> - move to matching character (default supported pairs: '()', '{}', '[]' - use
<code>:h matchpairs</code> in vim for more info)
</li>
<li>
<kbd>)</kbd> - jumps to the next sentence
</li>
<li>
<kbd>(</kbd> - jumps to the previous sentence
</li>
<li>
<kbd>}</kbd> - jump to next paragraph (or function/block, when editing code)
</li>
<li>
<kbd>{</kbd> - jump to previous paragraph (or function/block, when editing code)
</li>
<li>
<kbd>]]</kbd> - jump to next section
</li>
<li>
<kbd>][</kbd> - jump to the end of the next section
</li>
<li>
<kbd>[[</kbd> - jump to previous section
</li>
<li>
<kbd>[]</kbd> - jump to the end of the previous section
</li>
<li>
<kbd>Ctrl</kbd>+<kbd>o</kbd> - jump to the previous position
</li>
<li>
<kbd>Ctrl</kbd>+<kbd>i</kbd> - jump to the next position
</li>
</ul>
<h2>
Cursor movement around the screen</h2>
<ul>
<li>
<kbd>h</kbd> - move cursor left
</li>
<li>
<kbd>j</kbd> - move cursor down
</li>
<li>
<kbd>k</kbd> - move cursor up
</li>
<li>
<kbd>l</kbd> - move cursor right
</li>
<li>
<kbd>zz</kbd> - center cursor on screen
</li>
<li>
<kbd>H</kbd> - move to top of screen
</li>
<li>
<kbd>M</kbd> - move to middle of screen
</li>
<li>
<kbd>L</kbd> - move to bottom of screen
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>e</kbd> - move screen down one line (without moving cursor)
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>y</kbd> - move screen up one line (without moving cursor)
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>b</kbd> - move back one full screen
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>f</kbd> - move forward one full screen
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>d</kbd> - move forward 1/2 a screen
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>u</kbd> - move back 1/2 a screen
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>
Marks</h2>
<ul>
<li>
<kbd>:marks</kbd> - list of marks
</li>
<li>
<kbd>ma</kbd> - set current position for mark A
</li>
<li>
<kbd>`a</kbd> - jump to position of mark A
</li>
<li>
<kbd>'a</kbd> - jump to line with mark A
</li>
<li>
<kbd>``</kbd> - position before the last jump within current file
</li>
</ul>
<h2>
Search and replace</h2>
<ul>
<li>
<kbd>:g/TODO</kbd> - show all lines with TODO
</li>
<li>
<kbd>/pattern</kbd> - search for pattern
</li>
<li>
<kbd>?pattern</kbd> - search backward for pattern
</li>
<li>
<kbd>\vpattern</kbd> - 'very magic' pattern: non-alphanumeric characters are interpreted as special regex
symbols (no escaping needed)
</li>
<li>
<kbd>n</kbd> - repeat search in same direction
</li>
<li>
<kbd>N</kbd> - repeat search in opposite direction
</li>
<li>
<kbd>:%s/old/new/g</kbd> -
replace all old with new throughout file
</li>
<li>
<kbd>:%s/old/new/gc</kbd> -
replace all old with new throughout file with confirmations
</li>
<li>
<kbd>:set hlsearch</kbd> - highlighting of search matches
</li>
<li>
<kbd>:noh</kbd> - remove highlighting of search matches
</li>
</ul>
<h2>
Search in multiple files</h2>
<ul>
<li>
<kbd>:vimgrep /pattern/ {`{file}`}</kbd> -
search for pattern in multiple files
</li>
<li>
<kbd>:copen</kbd> - show all found places
</li>
<li>
<kbd>:close</kbd> - close window with results
</li>
<li>
<kbd>cn</kbd> - go to the next result
</li>
<li>
<kbd>cp</kbd> - go to the previous result
</li>
</ul>
</div>
<div class="grid-block">
<div class="well">
<strong>Tip</strong>
<p>Use of "\v" means that after it, all ASCII characters except '0'-'9', 'a'-'z',
'A'-'Z' and '_' have special meaning: "very magic"
<br/>
Use of "\V" means that after it, only a backslash and the terminating
character (usually / or ?) have special meaning: "very nomagic"
</p>
<table>
<tr> <td>after:</td><td> \v</td><td>\m </td> <td>\M</td> <td>\V</td> <td>matches</td> </tr>
<tr><td></td><td>         </td><td>'magic'</td><td>'nomagic'</td></tr>
<tr> <td></td> <td>a</td> <td>a</td> <td>a</td> <td>a</td> <td>literal 'a' </td> </tr>
<tr> <td></td> <td>\a</td> <td>\a</td> <td>\a</td> <td>\a</td> <td>any alphabetic character </td> </tr>
<tr> <td></td> <td>.</td> <td>.</td> <td>\.</td> <td>\.</td> <td>any character</td> </tr>
<tr> <td></td> <td>\.</td> <td>\. </td> <td>. </td> <td>.</td> <td>literal dot</td> </tr>
<tr> <td></td> <td>$ </td><td> $ </td> <td> $ </td><td> \$ </td><td> end-of-line</td> </tr>
<tr> <td></td> <td>* </td><td> * </td> <td> \* </td><td> \* </td><td> any number of the previous atom</td> </tr>
<tr> <td></td> <td>~ </td><td> ~ </td> <td> \~ </td><td> \~ </td><td> latest substitute string</td> </tr>
<tr> <td></td> <td>() </td><td> \(\)</td> <td> \(\)</td><td> \(\)</td><td> group as an atom</td> </tr>
<tr> <td></td> <td>| </td><td> \| </td> <td> \| </td><td> \| </td><td> nothing: separates alternatives</td> </tr>
<tr> <td></td> <td>\\ </td><td> \\ </td> <td> \\ </td><td> \\ </td><td> literal backslash</td> </tr>
<tr> <td></td> <td>\{ </td><td> { </td> <td> { </td><td> { </td><td> literal curly brace</td></tr>
</table>
</div> </div>
<!--FILES-->
<div class="grid-block">
<div class="grid-lg-1-3">
<h2>
Working with multiple files</h2>
<ul>
<li>
<kbd>:e file</kbd> - edit a file in a new buffer
</li>
<li>
<kbd>:ls</kbd> - list all open buffers
</li>
<li>
<kbd>:bnext</kbd> or <kbd>:bn</kbd> or <kbd>Ctrl</kbd> + <kbd>^</kbd> -
go to the next buffer
</li>
<li>
<kbd>:bprev</kbd> or <kbd>:bp</kbd> -
go to the previous buffer
</li>
<li>
<kbd>:bd</kbd> - delete a buffer (close a file)
</li>
<li>
<kbd>:sp file</kbd> - open a file in a new buffer and split window
</li>
<li>
<kbd>:vsp file</kbd> - open a file in a new buffer and vertically split window
</li>
</ul>
<h2>
Working with Tabs</h2>
<ul>
<li>
<kbd>:tabnew</kbd> or <kbd>:tabnew {page.words.file}</kbd> -
open a file in a new tab
</li>
<li>
<kbd>gt</kbd> or <kbd>:tabnext</kbd> or <kbd>:tabn</kbd> - move to the next tab
</li>
<li>
<kbd>gT</kbd> or <kbd>:tabprev</kbd> or <kbd>:tabp</kbd> - move to the previous tab
</li>
<li>
<kbd>#gt</kbd> - move to tab number #
</li>
<li>
<kbd>:tabmove #</kbd> or <kbd>:tabm #</kbd> - move current tab to the #th position
(indexed from 0). Also +# or -# move current tab to forward or backward on # positions
</li>
<li>
<kbd>:tabclose</kbd> or <kbd>:tabc</kbd> - close the current tab and all its windows
</li>
<li>
<kbd>:tabonly</kbd> or <kbd>:tabo</kbd> - close all tabs except for the current one
</li>
<li>
<kbd>:tabdo</kbd> command - run the <code>command</code> on all tabs (e.g. <code>:tabdo q</code> - closes
all opened tabs)
</li>
</ul>
</div>
<div class="grid-lg-1-3">
<h2>Working with windows</h2>
<ul>
<li>
<kbd>Ctrl</kbd> + <kbd>wT</kbd> - move the current split window into its own tab
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>ws</kbd> or <kbd>:sp {file}</kbd> - split window (and open file)
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wv</kbd> or <kbd>:vsp {file}</kbd> - split window vertically (and open file)
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wc</kbd> - close the active window
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wo</kbd> - keep only the active window, closing all others
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wq</kbd> - quit a window
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>w{hljk}</kbd> - move cursor to the left/right/below/above window
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>ww</kbd> - switch windows
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>w{HLJK}</kbd> - move window to the left/right/below/above window
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wr</kbd> - move windows clockwise (horizontal split)
</li>
<li>
<kbd>Ctrl</kbd> + <kbd>wr</kbd> - move windows counter-clockwise (horizontal split)
</li>
<li>
<kbd>[size]Ctrl</kbd> + <kbd>w_</kbd> - set height to 'size' or maximize height of the active window
</li>
<li>
<kbd>[size]Ctrl</kbd> + <kbd>w|</kbd> - set wight to 'size' or maximize width of the active window
</li>
<li>
<kbd>20Ctrl</kbd> + <kbd>w+</kbd>or<kbd>w-</kbd> - increase or decrease height of the active window on 20
</li>
<li>
<kbd>20Ctrl</kbd> + <kbd>w<</kbd>or<kbd>w></kbd> - increase or decrease wight of the active window on 20
</li>
</ul>
<div class="well">
<strong>Tip</strong> resize current window:</p>
<kbd>:res +5</kbd> increase size</p>
<kbd>:res -5</kbd> decrease size
</div>
</div>
<div class="grid-lg-1-3">
<h2>Folding</h2>
<ul>
<li>
<kbd>zc</kbd> - fold block
</li>
<li>
<kbd>zo</kbd> - unfold block
</li>
<li>
<kbd>zM</kbd> - fold all blocks
</li>
<li>
<kbd>zR</kbd> - unfold all blocks
</li>
<li>
<kbd>za</kbd> - invert folding
</li>
<li>
<kbd>zf</kbd> - fold selected in manual mode
</li>
<li>
<kbd>:set foldenable</kbd> - turn on folding
</li>
<li>
<kbd>:set foldmethod=</kbd><kbd>syntax</kbd> /<kbd>indent</kbd> /<kbd>manual</kbd> /<kbd>marker</kbd>
/<kbd>bigin,end</kbd>
</li>
</ul>
<h2>Spellcheck</h2>
<ul>
<li>
<kbd>:set spell spelllang=ru,en</kbd> - turn on spellcheck for russian & english
</li>
<li>
<kbd>:set nospell</kbd> - turn off spellchecking
</li>
<li>
<kbd>]s</kbd> - next word with mistake
</li>
<li>
<kbd>[s</kbd> - previous word with mistake
</li>
<li>
<kbd>z=</kbd> - change word by the one from the list
</li>
<li>
<kbd>zg</kbd> - mark word as <i>good</i>
</li>
<li>
<kbd>zw</kbd> - mark word as <i>wrong</i>
</li>
<li>
<kbd>zG</kbd> - ignore word
</li>
</ul>
<div class="well">
<strong>Tip</strong> add dictionaries from the
<a>http://ftp.vim.org/vim/runtime/spell/</a>
<pre>mkdir -p ~/.vim/spell
cd ~/.vim/spell
wget [vim.org]/ru.koi8-r.sug
wget [vim.org]/ru.koi8-r.spl
wget [vim.org]/en.ascii.sug
wget [vim.org]/en.ascii.spl</pre>
</div>
</div>
</div>
</div>
</div>
<style type="text/css">
/*Layout*/
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.grid-block {
clear: both;
letter-spacing: -0.31em;
display: -webkit-flex;
-webkit-flex-flow: row wrap;
display: -ms-flexbox;
-ms-flex-flow: row wrap;
overflow: hidden;
text-rendering: optimizespeed;
width: 100%;
}
.grid-block,
.grid-1,
.grid-lg-1-2,
.grid-lg-1-3 {
display: inline-block;
float: left;
letter-spacing: normal;
padding: 0px 10px;
vertical-align: top;
word-spacing: normal;
text-rendering: auto;
width: 100%;
zoom: 1;
}
/*Large*/
@media screen and (min-width: 64em) {
.grid-lg-1-2 {
width: 50%;
}
.grid-lg-1-3 {
width: 33.3333%;
}
}
/*-----------------*/
h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
color: inherit;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 500;
letter-spacing: normal;
line-height: 1.1;
margin-top: 20px;
margin-bottom: 10px;
}
h1 {
font-size: 45px;
margin-bottom: 0px;
margin-top: 30px;
}
h1 a {
color: #fa4949;
}
h2 {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 24px;
}
a {
color: #1a0dab;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
p {
margin: 12px 0;
line-height: 20px;
letter-spacing: normal;
}
ul {
padding: 0;
}
li {
border-bottom: 1px dashed #e0e0e0;
line-height: 1.55;
list-style-type: none;
padding: 7px 0;
}
kbd {
background-color: #eee;
color: #000;
border-radius: 0;
box-shadow: none;
font-family: 'Inconsolata', monospace;
font-size: 1em;
font-weight: bold;
padding: 4px;
}
.container {
background: #fff;
margin: 0 auto;
width: 100%;
max-width: 1170px;
}
.well {
background: #f8f8f8;
border: none;
border-radius: 0;
-webkit-box-shadow: none;
box-shadow: none;
min-height: 20px;
margin-bottom: 20px;
padding: 19px;
}
.link-to-repo {
margin: 20px 0;
text-align: center;
}
.link-to-repo p {
margin-bottom: inherit;
}
.ja {
font-size: 13px;
}
.ja h2 {
font-size: 20px;
}
.ja kbd {
font-size: 16px;
}
.box {
background: #f8f8f8;
border: 1px solid #e7e7e7;
margin: 10px 0;
width: 100%;
}
.box .grid-block {
padding: 0;
}
.box-header {
background: #ebebeb;
min-height: 30px;
padding: 10px;
}
.box-header h2 {
font-size: 16px;
margin: 5px 0;
}
.box-body {
font-size: 14px;
display: block;
clear: both;
overflow: hidden;
padding: 0 10px;
padding-bottom: 20px;
}
.box-body li {
border-bottom: none;
line-height: inherit;
list-style-type: none;
padding: 7px 7px;
}
@media print {
html,
body {
height: 90%;
}
body {
line-height: 1.3;
font-size: 10px;
}
h1 {
font-size: 20px;
margin-left: 0;
margin-bottom: 8px;
margin-top: 0;
}
h2 {
font-size: 10px;
margin-bottom: -10px;
margin-top: 0;
line-height: 10px;
}
#footer,
.link-to-repo {
height: 0px;
display: none;
}
.commands-container {
clear: both;
overflow: hidden;
}
.commands-container>.grid-block:first-child {
width: 60%;
}
.commands-container>.grid-block:last-child .grid-lg-1-3 {
width: 50%;
}
.grid-block {
clear: none;
float: left;
display: inline-block;
overflow: hidden;
width: 40%;
}
.grid-1 {
padding: 0;
margin: 0;
}
.grid-lg-1-3 {
display: inline-block;
float: left;
letter-spacing: normal;
padding: 0;
word-spacing: normal;
text-rendering: auto;
width: 33.3333%;
zoom: 1;
}
li {
line-height: initial;
padding: 4px 5px;
}
kbd {
color: #000;
font-size: 12px;
padding: 0;
}
.well {
height: 0px;
display: none;
}
}
@page {
margin: 0.5cm;
}
</style>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-28395816564224050542020-02-08T12:44:00.000+03:002020-02-08T12:44:06.604+03:00Ускоряем реализацию fs2 стримов для reactive-streams<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfYEi1RuUfULtCeT4UXl9O85zSjEV2jqOUm1n2pTtV7OBXp97z7mMx31NYm8SJMn4qEbJSYYHg8H9WDC3_K2e9ugSL02IpoINgxzMOOW2XztwnvdYsBS1cd5hG4MxUqWj5a7WrUYXPLRPA/s1600/reactivefs2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="400" data-original-width="400" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfYEi1RuUfULtCeT4UXl9O85zSjEV2jqOUm1n2pTtV7OBXp97z7mMx31NYm8SJMn4qEbJSYYHg8H9WDC3_K2e9ugSL02IpoINgxzMOOW2XztwnvdYsBS1cd5hG4MxUqWj5a7WrUYXPLRPA/s200/reactivefs2.png" width="200" /></a></div>
</td><td>Если вы, как и я, не в восторге от текущих инструментов для коммуникации с MongoDB, и меньшим злом выбрали для себя выбрали <a href="https://mongodb.github.io/mongo-java-driver-reactivestreams/1.13/" target="_blank">MongoDB Reactive Streams Java Driver</a>, в надежде на <a href="https://fs2.io/" target="_blank">существующую интеграцию</a> reactive-streams с fs2, то вероятно, вас ждет разочарование. Официальная интеграция оооооооооооооооооооооооочень медленная. Если вы не собираетесь запускать ваше добро на JS, то возможно вам пригодится мое решение: <a href="https://github.com/dokwork/fast-reactive-fs2">https://github.com/dokwork/fast-reactive-fs2</a>.<br />
Просто оставлю результат замеров здесь:<br />
<div class="text">
<pre>Benchmark Mode Cnt Score Error Units
ReadOneMillionNumbers.dokworkStreamSubscriber avgt 25 39.377 ± 2.749 ms/op
ReadOneMillionNumbers.fs2StreamSubscriber avgt 25 11189.737 ± 2213.225 ms/op
</pre>
</div>
<br /></td></tr>
</tbody></table>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com2tag:blogger.com,1999:blog-7407396207758383724.post-6517694583307754182019-10-16T11:29:00.000+03:002019-10-17T23:49:25.271+03:00Разбираемся с сагами на Scala Russia Meetup 2019<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/IWOHhwkpJgQ/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/IWOHhwkpJgQ?feature=player_embedded" width="480"></iframe></div>
<br /></div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-51544975303948540822019-05-24T21:21:00.001+03:002020-02-08T12:21:23.825+03:00Разбор реализации актора из книги FP in Scala<div dir="ltr" style="text-align: left;" trbidi="on">
<table>
<tbody>
<tr>
<td><img alt="cover of the book" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2ftIJJR_zXCLmOjPWlAGSsjUZzgmlt4-OqBiqneoVh9QaEdXZ1NX2vyw3UeQ8fXQ-TwyeEycrZGcxWgLXK0C8cWpDiQFb_CjAm3PNbLU3xdW5kGdpH-fBNlyYVKKrFUFVXNccEbtsUDNo/s200/rbcover.jpg" width="140" /></td>
<td>В книге <a href="https://www.manning.com/books/functional-programming-in-scala">Functional Programming in Scala</a>, в главе <em>Purely functional parallelism</em>, в конечной реализации <code>Par[a]</code>, авторы ссылаются на акторную модель вычислений и приводят в пример ее реализацию: <a href="https://github.com/fpinscala/fpinscala/blob/master/exercises/src/main/scala/fpinscala/parallelism/Actor.scala">Actor.scala</a>. В основе реализации приводимого актора лежат идеи <a href="http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue">Non-intrusive MPSC node-based queue</a>, которые не могут не вызывать восторга! В своей статье хочу попытаться разъяснить для себя и всех, кому это будет интересно, эти идеи.</td>
</tr>
</tbody></table>
<br />
<a name='more'></a>В основе акторной модели лежит идея о том, что каждый актор обладает собственным mailbox-ом, который может пополняться сообщениями в конкурентной манере, а разбираться исключительно синхронно. Для реализации mailbox-а актора нужна соответствующая <strong>M</strong>ulti-<strong>P</strong>roducers-<strong>S</strong>ingle-<strong>C</strong>onsumer очередь. Давайте представим ее в виде односвязного списка с двумя заголовками <code>head</code> и <code>tail</code> . Первый будет указывать на конец списка в который будут добавляться новые элементы, второй указатель будет ссылать на конец списка, с которого будет происходить чтение очереди (<em>названия взяты из оригинальной статьи и, вообще говоря, противоречат общепринятым</em>).<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIRwI4DrxEhIyM3aOvWz3A_zbbOBno1PM3mA51YSM93WPh-ZkDfuRmbBl9Zv3kjpUaSXjjMGbqIhX-ASDJCDu3vPl9yLiMasqtRuUn5Nna5ixn67GTZ0mPKHXoncgns638wCYBwB7NvdHQ/s1600/queue1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="42" data-original-width="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIRwI4DrxEhIyM3aOvWz3A_zbbOBno1PM3mA51YSM93WPh-ZkDfuRmbBl9Zv3kjpUaSXjjMGbqIhX-ASDJCDu3vPl9yLiMasqtRuUn5Nna5ixn67GTZ0mPKHXoncgns638wCYBwB7NvdHQ/s1600/queue1.png" /></a></div>
<br />
Каждый элемент такого списка размещен в ячейке:<br />
<div class="code">
<pre><code class="scala">class Node[A](var a: A = null.asInstanceOf[A]) extends AtomicReference[Node[A]] {
// для наглядности опишем явно метод `next`
def next: Node[A] = get()
// и метод для изменения ссылки на следующую ячейку
def next_=(n: Node[A]): Unit = lazySet(n)
}</code></pre>
</div>
<h3 id="запись-в-очередь">
Запись в очередь</h3>
В самом начале <code>head</code> и <code>tail</code> указывают на одну и ту же пустую ячейку:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs-hqH0kAGgPFmkonL5d9P3y0QpySyjizm_arLE5hYqBf12f6-5kCgbWiORU4pkBdB3pVGKrJBr47d2C7E-YxWTXdWjaSpoDYP_vIu4KKKgAWw2ijW4EysvaqiQ-yLbJuyiF3-TN3zT4qN/s320/queue2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="44" data-original-width="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgs-hqH0kAGgPFmkonL5d9P3y0QpySyjizm_arLE5hYqBf12f6-5kCgbWiORU4pkBdB3pVGKrJBr47d2C7E-YxWTXdWjaSpoDYP_vIu4KKKgAWw2ijW4EysvaqiQ-yLbJuyiF3-TN3zT4qN/s320/queue2.png" /></a></div>
<br />
<div class="code">
<pre><code class="scala">class MPSCQueue[A] {
private val head = new AtomicReference[Node[A]](new Node[A]())
private val tail = new AtomicReference[Node[A]](head.get())
}</code></pre>
</div>
Рассмотрим внимательно процесс добавления элементов в очередь:<br />
<div class="code">
<pre><code class="scala">def put(a: A): Unit = {
val n = new Node(a)
// получаем ссылку на последнюю добавленную ячейку
// и двигаем указатель `head` на новую
val last = head.getAndSet(n)
// теперь за последней ячейкой есть еще одна
last.next = n
}</code></pre>
</div>
Процесс вставки элементов:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAbUPhGew9rHjGQqioNh-Xo0dNB7On6BnGSHu7ZHa3M3Hql5mRX9-rinSvN97rP_bOfbNnoETxqdEKM3MMZIuA0_ihSjQsDHGclq4YQXV6E47z41uuYXc4k5IoFDiIv603UUe6TlNBAEKy/s1600/queue3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="762" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAbUPhGew9rHjGQqioNh-Xo0dNB7On6BnGSHu7ZHa3M3Hql5mRX9-rinSvN97rP_bOfbNnoETxqdEKM3MMZIuA0_ihSjQsDHGclq4YQXV6E47z41uuYXc4k5IoFDiIv603UUe6TlNBAEKy/s1600/queue3.png" /></a></div>
<br />
Обратите внимание на то, что между изменением указателя <code>head</code> и обновлением ссылки на новую ячейку, следующую за последней, может случиться гонка, и некоторый второй поток может успеть полностью выполнить метод <code>put</code>!<br />
Представим себе гонку при вставке самого первого элемента в очередь. Первый поток успевает получить ссылку на пустую ячейку S, но не успевает изменить указатель <code>S.next</code>:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW6WWo7cmmhsgRtBSWF1et0-dkZpgrmZFoGFeeQiVPKE9Dn1Amx5Nqa7osdZ5pI-eQJHHU9tLFRoxCnqmpx9706fU1JG64eOu1vKMLbo0JOAumjJkcSKdPP4vuGO_TT5tD-6igNhAQBV2L/s1600/queue_1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="121" data-original-width="482" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiW6WWo7cmmhsgRtBSWF1et0-dkZpgrmZFoGFeeQiVPKE9Dn1Amx5Nqa7osdZ5pI-eQJHHU9tLFRoxCnqmpx9706fU1JG64eOu1vKMLbo0JOAumjJkcSKdPP4vuGO_TT5tD-6igNhAQBV2L/s1600/queue_1.png" /></a></div>
<br />
Теперь второй поток начинает выполнять метод <code>put</code> и в качестве последней ячейки видит ячейку <code>N1</code>, но указатель <code>head</code> устанавливает на новую ячейку <code>N2</code>, при этом связывая <code>N2</code> как следующую за <code>N1</code>:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAqA7BxoL7OSIcjtb3Z1pwkT0QHgPT4A3hyv6zzsVyFVvAG1QyH2_KmyOM5inbSu91sfT-GwZS2yIOUlE-OJRq4d6S40OUOAuM45_FeU2-7UA059I0B8kf9s6rF-YLnWPOtkggCTrcUZrW/s1600/queue_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAqA7BxoL7OSIcjtb3Z1pwkT0QHgPT4A3hyv6zzsVyFVvAG1QyH2_KmyOM5inbSu91sfT-GwZS2yIOUlE-OJRq4d6S40OUOAuM45_FeU2-7UA059I0B8kf9s6rF-YLnWPOtkggCTrcUZrW/s1600/queue_2.png" /></a></div>
<br />
В этот момент вся очередь приходит в неконсистентное состояние, т.к. со стороны указателя <code>tail</code>, с которого выполняется чтение очереди, очередь пуста!<br />
Но, когда первый поток продолжит свое выполнение, он восстановит утерянную связь между <code>S</code> и <code>N1</code> (тк его указатель <code>last</code> ссылается на <code>S</code>, а в качестве <code>S.next</code> будет задана <code>N1</code>), и очередь снова станет консистентной!<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNEcrJSyZx9uUMyQS3pyE67gRQvSgBLf6z52KTikTgrLsc1vhAxeVmXfdFvTx6CnxKOVVEP2uFWVzs8kwLFFvNv4Naj7eC4vNciT1SNXegUByr_hZGEXL5-_ibOg7fxz9ysOBZ3QofQAHz/s1600/queue_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="202" data-original-width="201" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNEcrJSyZx9uUMyQS3pyE67gRQvSgBLf6z52KTikTgrLsc1vhAxeVmXfdFvTx6CnxKOVVEP2uFWVzs8kwLFFvNv4Naj7eC4vNciT1SNXegUByr_hZGEXL5-_ibOg7fxz9ysOBZ3QofQAHz/s1600/queue_3.png" /></a></div>
<br />
<h3 id="чтение-из-очереди">
Чтение из очереди</h3>
Подразумевается, что чтение рассматриваемой очереди выполняется синхронно, поэтому здесь не предлагается никаких средств синхронизации. В условиях отсутствия гонки, алгоритм получения элемента из очереди крайне прост:<br />
<div class="code">
<pre><code class="scala">def pop(): Option[A] = {
// tail всегда указывает на пустую ячейку
val first = tail.get().next
if (first ne null) {
val a = first.a
first.a = null.asInstanceOf[A]
tail.lazySet(first)
Some(a)
} else None
}</code></pre>
</div>
<h3 id="реализация-актора">
Реализация актора</h3>
Синхронизация чтения выполняется уже в реализации цикла обработки сообщений актора. Для этого подойдет <code>lock</code> на основе <code>AtomicBoolean</code>:<br />
<div class="code">
<pre><code class="scala">val lock = new AtomicBoolean(false)
def tryHandle(): Unit = {
// если процесс разбора еще не был запущен,
// запустим его, захватив при этом lock
if (lock.compareAndSet(false, true)) handle()
}
def handle(): Unit = mailbox.pop() match {
case Some(msg) ⇒
// если очередь не пуста, обработаем сообщение
receive(msg)
// и продолжим разбор mailbox
handle()
case None ⇒
// когда очередь опустеет, отпустим lock
lock.lazySet(false)
}
}</code></pre>
</div>
Процесс разбора очереди можно пытаться запускать снова и снова при получении новых сообщений:<br />
<div class="code">
<pre><code class="scala">def apply(msg: A): Unit = {
mailbox.put(msg)
tryHandle() // процесс будет запущен только 1 раз, благодарая lock-у
}</code></pre>
</div>
У описанной реализации цикла обработки сообщений есть пара недостатков. Во-первых, обработка сообщений будет происходить в том же потоке, что и отправка сообщения. Чтобы этого избежать, каждый вызов метода <code>handle</code> можно выполнять на отдельном потоке из пула (<em>на самом деле сложно однозначно сказать какое решение лучше, поэтому авторы приводят реализацию актора, в которой данное поведение задается пользователем явным образом</em>):<br />
<div class="code">
<pre><code class="scala">def handle(): Unit = ec.execute {
() ⇒ mailbox.pop() match {
case Some(msg) ⇒
receive(msg)
handle()
case None ⇒
lock.lazySet(false)
}
}</code></pre>
</div>
Во-вторых, цикл обработки может быть прерван, но при этом сообщения в очереди могут все еще оставаться. Это может произойти в случае, когда между получением результата от <code>pop</code> и обновлением lock-а (в ветке с <code>None</code> до <code>lazySet</code>) придет новое сообщение, но тк <code>lock</code> еще не был отпущен, а <code>pop</code> вернул None, цикл обработки сообщений может прерваться. Чтобы этого не произошло, попытаемся запустить его:<br />
<pre class=" language-scala"><code class="prism language-scala"><span class="token keyword">def</span> handle<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Unit</span> <span class="token operator">=</span> ec<span class="token punctuation">.</span>execute <span class="token punctuation">{</span>
<span class="token punctuation">(</span><span class="token punctuation">)</span> ⇒ mailbox<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">match</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> Some<span class="token punctuation">(</span>msg<span class="token punctuation">)</span> ⇒
receive<span class="token punctuation">(</span>msg<span class="token punctuation">)</span>
handle<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">case</span> None ⇒
lock<span class="token punctuation">.</span>lazySet<span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>mailbox<span class="token punctuation">.</span>nonEmpty<span class="token punctuation">)</span> tryHandle<span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
Где <code>receive</code> - пользовательская функция для реагирования на сообщения, <code>nonEmpty</code> - метод очереди реализованный как:<br />
<div class="code">
<pre><code class="scala">def nonEmpty: Boolean = tail.get.next ne null</code></pre>
</div>
Исходный код полного решения описанного в статье вы можете найти на <a href="https://gist.github.com/dokwork/6e81b94ba2b9fd3726f0fd9b446a903d" target="_blank">github-е</a>.<br />
<br />
В решении, предлагаемом авторами книги, можно заметить дополнительную оптимизацию, которая позволяет за один вызов метода <code>handle</code> обработать более одного сообщения, но она тривиальна и не нуждается в дополнительном разъяснении.<br />
<br />
На этом для себя главу считаю освоенной, тк прочие аспекты куда проще для понимания :)</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-14621465253001842642019-04-14T16:51:00.000+03:002019-04-14T16:51:02.218+03:00Что значит "писать в функциональном стиле"?<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/-9veviJN2WQ/0.jpg" frameborder="0" height="316" src="https://www.youtube.com/embed/-9veviJN2WQ?feature=player_embedded" width="540"></iframe>
</div>
Ссылка на презентацию: <a href="https://docs.google.com/presentation/d/e/2PACX-1vTK-gVolfjXI7CDFhg-hMfMudpBJD8GuzMyefuOUOdxY_mMVfnZjvO1mq_NMw5qRKhHv9p1iCCY0c43/pub">тыц</a>.
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-41554843199239792872018-04-17T10:56:00.000+03:002019-04-14T16:54:22.041+03:00Дебют на scala meetup<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="separator" style="clear: both; text-align: center;">
<iframe 540="" allow="autoplay; encrypted-media" allowfullscreen="" class="YOUTUBE-iframe-video" frameborder="0" height="316" width="440" src="https://www.youtube.com/embed/dWyGM3MnN0A?list=PL9SJrES3EGURfczfW0KSGXVDrykc6mJuo"></iframe>
</div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com2tag:blogger.com,1999:blog-7407396207758383724.post-70355935285793997602017-10-28T11:29:00.000+03:002018-01-28T16:34:22.600+03:00Шаблонные проекты с Giter8<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td style="word-wrap: normal;"><img border="0" data-original-height="397" data-original-width="595" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6O-mBP4dEPC_8GxZK6aOQJ2SQIvLwK8oxg7FeS9YRdTDqQy_b0sNbZLLOt1ZhKIRQW5tFTHt2MUclcYp5TlYxDDfKtDqhTY70Z5tAPVKkCk8g2lAmpdBxCX77skk9XYtbSTs7xGuf5yCR/s200/giter8.png" width="200" />
</td><td>Надоело копировать build.sbt, plugins.sbt, .travis.yml и прочие шаблонные файлы каждый раз, когда садитесь за новый проект? Тогда у меня для вас есть хорошая новость: забудьте об этом и встречайте <a href="http://www.foundweekends.org/giter8/" target="_blank"><b>giter8</b></a>!</td></tr>
</tbody></table>
<a name='more'></a> Giter8 - консольная утилита для управления шаблонами, хранящимися в git-репозитории. Но использовать ее мы конечно не будем :). Начиная с версии 0.13.13, sbt включает в себя возможность использования шаблонов проектов g8:<br />
<div class="console">
sbt new scala/hello-world.g8
</div>
Эта команда скачает все файлы отсюда: <a href="https://github.com/scala/hello-world.g8/tree/master/src/main/g8" target="_blank">https://github.com/scala/hello-world.g8/tree/master/src/main/g8</a> к вам на машину.<br />
<br />
<h3 style="text-align: left;">
Как создать свой шаблон</h3>
Все что вам нужно для создания собственного шаблона - это репозиторий с суфиксом <span style="font-family: "courier new" , "courier" , monospace;">.g8</span>.<br />
<h4 style="text-align: left;">
Структура репозитория </h4>
Структура репозитория должна соответствовать следующим не хитрым требованиям:<br />
<br />
<ul style="text-align: left;">
<li>Если в репозитории есть директория <span style="font-family: "courier new" , "courier" , monospace;">src/main/g8</span>, то в качестве шаблона будут использоваться файлы из этой директории</li>
<li>В противном случае сам репозиторий будет представлять собой шаблон</li>
</ul>
<div>
<h4 style="text-align: left;">
Шаблонизация содержимого файлов</h4>
</div>
<div>
Для большей гибкости и удобства, giter8 предлагает механизм шаблонизации некоторых значений, на пример, чтобы иметь возможноть переопределить название нового проекта в <span style="font-family: "courier new" , "courier" , monospace;">build.sbt</span>:</div>
<div class="text" title="build.sbt">
<pre>lazy val root = (project in file("."))
.settings(
name := "$name$"
)</pre>
</div>
Работает все очень просто. Для определения используемых шаблонов, необходимо в репозиторий добавить файл <span style="font-family: "courier new" , "courier" , monospace;">default.properties</span>.
<br />
<div class="text" title="default.properties">
<pre>name = my project</pre>
</div>
Он может находиться либо в директории <span style="font-family: "courier new" , "courier" , monospace;">project/</span>, либо в корневой директории. Формат файла крайне прост: на каждой строке указывается название шаблона, а через знак = его значение поумолчанию. Описанные в default.properties шаблоны могут быть использованы в любом файле в репозитории, для этого шаблон должен быть заключен между двумя знаками $, как в примере выше. Актуальные значения для шаблонов будут запрошены при созданни нового проекта из шаблона. Более подробно можно прочитать <a href="http://www.foundweekends.org/giter8/template.html" target="_blank">здесь</a>.<br />
<b><br /></b>
<b>Внимание!</b> Если вы используете в ваших файлах символ <span style="font-family: "courier new" , "courier" , monospace;">$</span>, то вы должны его экранировать: <span style="font-family: "courier new" , "courier" , monospace;">\$</span>, иначе создание нового проекта завершится с ошибкой <span style="font-family: "courier new" , "courier" , monospace;">Exiting due to error in the template</span>.<br />
<br />
<h4 style="text-align: left;">
Пример шаблона проекта</h4>
Пример моего шаблона для scala проекта можно посмотреть <a href="https://github.com/dokwork/sbt-1.0.g8" target="_blank">здесь</a>.
<br />
<div class="console">
sbt new dokwork/scala-project.g8
</div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-79039577762236510872017-03-09T08:22:00.000+03:002017-03-09T08:22:25.253+03:00Первая за 6 лет статья на Хабр<div dir="ltr" style="text-align: left;" trbidi="on">
<table style="text-align: center;"><tbody>
<tr><td><img border="0" height="80" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiAkZxpmhPO_PEfvbStGk3bEM8Z0-pa-TwWkrfuSi5gNmIqqpu_Il2pFPoV8cmv_KUVvpiJFbqco8XNFmttQCp_a3l4mh_rFFmgzOBqozhSVI3_nWYqISqBsFvdJQfs0DZQY_Ru_VBodUg/s200/HbQGtx57.jpg" width="80" /></td>
<td><h2>
<a href="https://habrahabr.ru/post/323052/" target="_blank">Безопасный Builder на Scala и Java</a>
</h2>
</td></tr>
<tr>
</tr>
</tbody></table>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-8724784223108734032017-02-05T20:13:00.000+03:002017-02-15T21:31:29.401+03:00Состояния java.lang.Thread на граблях и примерах<div dir="ltr" style="text-align: left;" trbidi="on">
<div>
<div style="float: left;">
<a href="http://www.plantuml.com/plantuml/png/dLJ1IiD04BtlLomzLQ4_eE31emSF8dWG3z6i6erfQRUM8a8LH4L1yEH15F-W5gl5sleBit_acyqctOWKDT0mE_DctjkPINMaOoNfYSRcn9pYVKCZ6fWRIPyum47lr9NKaql1OI14GmTQoMNLSjPACqc2JLGu4XAFSweWRFjdWA9anthMPpTafRBWOQmbA_LePrTp80NFINhC183UND6pwL2NHYnio0PKTX3jcKiQGEIOFYIzq92bCnpoWHjHFxrFIfNMziYO6rIttnEe3nxK7xY-C1-u0E3mU8g8gKJjICLXUMQ-248SRH77hgUbrUX0XN8jY3J29HaqfFMuiFExqs9StThFOwkgVX0bi48XRAV3QYj1UHuOVlav6ziSJwYkML4-oGpZrve4OxNDzx62a1qhLIjh7AfDNIXInErLe-GNi1Qkvi7IFbGAUkdEpgEFhN30ds4Fz8g9lAGpucc-mUnHttJC1Hn3EzThBtTotUBgyHOW2xaevNSY6wsRXrDVk9qVjIr0r3d-gTVF_hFYPWrPRc5-eln0Fcyq_mtWMnCE-btglG-pGiqPavbp_ZY4cEZATmcx-0K0" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5N0dDzW9d0QSVm-WJG6c7MJ1YKmFdL-2OZJbP_6EFEpLqwkuWjQgUT-5PFQstqVMZHnplLNAf9nLFSoQ_g0qGdhtq9WElT2WO43o4gFzO0Htm9JY9qMjChnQyb7ZW4bCm6rGrs663Y76I/s640/thread_states.png" height="189" width="320" /></a>
</div>
Каждый java разработчик знает что такое поток, как его запустить и, возможно, поменять ему приоритет или даже сделать его демоном. Сегодня этих поверхностных знаний зачастую достаточно для того, чтобы успешно справляться со своими повседневными задачами, в которых крутые фреймворки всеми силами пытаются скрыть от нас нюансы многопоточности. Но иногда жизнь заставляет спустится <strike>на дно</strike> на уровень ниже и познакомиться с нюансами работы с потоками более детально.<br />
<br />
В этой статье, по мере решения простых задач, через серию проб и ошибок, мы рассмотрим некоторые нюансы при работе с классом Thread в java, поговорим о том, какие у потоков бывают состояния и при каких условиях поток переходит из одного состояния в другое.
</div>
<a name='more'></a>
<div class="separator" style="clear: both;">
<h3 style="text-align: left;">
Задача про шагающего робота</h3>
<b>Условия задачи:</b><br />
<blockquote>
Надо написать робота который умеет ходить. За движение каждой его ноги отвечает отдельный поток. Шаг выражается в выводе в консоль LEFT или RIGHT. </blockquote>
</div>
В общем случае каркас решения может выглядеть следующим образом:<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class Robot {
class Leg implements Runnable {
private final String name;
Leg(String name) {
this.name = name;
}
@Override
public void run() {
while(true) {
step();
}
}
private void step() {
System.out.println(name);
}
}
Leg left = new Leg("LEFT");
Leg right = new Leg("RIGHT");
void run() {
new Thread(left).start();
new Thread(right).start();
}
public static void main(String[] args) {
new Robot().run();
}
}</code></pre>
</div>
Нам остается только переписать метод run так, чтобы вывод в консоль выглядел чередой не повторяющихся строк LEFT и RIGHT:
<br />
<div class="console">
<pre>LEFT
RIGHT
LEFT
RIGHT
...</pre>
</div>
<h4 style="text-align: left;">
Вариант первый: разделяемое состояние.</h4>
Идея проста: присвоим каждой ноге <span style="font-family: "courier new" , "courier" , monospace;">true</span> или <span style="font-family: "courier new" , "courier" , monospace;">false</span>, заведем разделяемое между потоками значение текущей ноги и на каждой итерации в каждой ноге будем сверять текущее значение разделяемой переменной с внутренним:
<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class RobotInfinitloop {
boolean currentLeg = true;
class Leg implements Runnable {
private final String name;
private final boolean leg;
Leg(String name, boolean leg) {
this.name = name;
this.leg = leg;
}
@Override
public void run() {
while(true) {
if (leg == currentLeg) {
step();
currentLeg = !leg;
Thread.yield();
}
}
}
private void step() {
System.out.println(name);
}
}
Leg left = new Leg("LEFT", false);
Leg right = new Leg("RIGHT", true);
void run() {
new Thread(left).start();
new Thread(right).start();
}
public static void main(String[] args) {
new RobotInfinitloop().run();
}
}</code></pre>
</div>
Если запустить этот код, можно обнаружить, что прогулка нашего робота будет совсем не такой долгой как нам хотелось и в некоторых случаях может и вовсе ограничиться одним единственным шагом.<br />
<br />
Все из-за того, что для оптимизации производительности для каждого потока создается локальная копия переменной currentLeg, изменения которой не видны другому потоку. Для решения этой проблемы существует ключевое слово volatile, которое говорит о том, что операция над переменной совершенная в одном потоке, должна быть видна в других.<br />
<div class="code" id="code_initial">
<pre><code class="scala">...
volatile boolean currentLeg = true;
...</code></pre>
</div>
Теперь два потока бегая в бесконечных циклах и пытаясь перехватить друг у друга разделяемый ресурс, решают нашу задачу.<br />
<br />
Обратите внимание на инструкцию Thread.yield(). Метод yield переводит состояние потока из Running в Ready и позволяет планировщику переключиться на другой поток. Конкретно в нашем примере наличие или отсутствие вызова данного метода не сильно скажется на результате, но на практике может позволить сделать переключение между потоками более предсказуемым.<br />
<br />
Но что на счет эффективности нашего решения? Наше приложение порождает два потока, которые не останавливаясь производят вычисления. Если открыть системный монитор в операционной системе, или запустить VisualVM, то можно заметить огромное потребление ресурсов CPU нашей программой. Пока один поток производит вывод в system out, другой наворачивает циклы в пустую. Чем дольше выполняет полезную нагрузку один поток, тем больше пустой работы выполняет второй:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKoKrz6wP7_YPX6RLktsEO6Bp_4jIpde3uifxidsj4QOLadNMjlyajxYE2U-ZGZG-bdJAsnsm8yIiWb0u6Lj6E9CTTzH_STYFr69XGGVHGMSoUzy4XmtK8jnAwrZRa2KHypMIcalJ_VWhW/s1600/yield+jinfinit+loop.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKoKrz6wP7_YPX6RLktsEO6Bp_4jIpde3uifxidsj4QOLadNMjlyajxYE2U-ZGZG-bdJAsnsm8yIiWb0u6Lj6E9CTTzH_STYFr69XGGVHGMSoUzy4XmtK8jnAwrZRa2KHypMIcalJ_VWhW/s320/yield+jinfinit+loop.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Показания VisualVM</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdrtaIf7okdmtYCzih1lfYUzsh1aw1JwbUk5KbYB8IO_PDtnkFEUR_9z4EYJtsSJExXcPnIxtCEhgx3cClPwfyABsa25ir2Iqt_Asl4FLM652p5Erkv9qj8hqyBLPj-Lgrnf9noQX0yXNg/s1600/yield+infinit+loop.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="101" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdrtaIf7okdmtYCzih1lfYUzsh1aw1JwbUk5KbYB8IO_PDtnkFEUR_9z4EYJtsSJExXcPnIxtCEhgx3cClPwfyABsa25ir2Iqt_Asl4FLM652p5Erkv9qj8hqyBLPj-Lgrnf9noQX0yXNg/s320/yield+infinit+loop.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Показания System Monitor</td></tr>
</tbody></table>
<h4 style="text-align: left;">
Вариант второй: общий монитор</h4>
Было бы здорово останавливать выполнение одного потока на время работы другого, а затем просыпаться и останавливать второй поток.<br />
<br />
В классе Thread есть методы <span style="font-family: "courier new" , "courier" , monospace;">suspend()</span> и <span style="font-family: "courier new" , "courier" , monospace;">resume()</span>, но они помечены как устаревшие и считаются опасными для использования.<br />
<br />
Чтобы ответить на вопрос почему, давайте представим, что в программе, выполняющейся в потоке, есть работа с критическими ресурсами (на пример System.out), доступ к которым мы должны получать только через <a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%B8%D1%82%D0%BE%D1%80_(%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F)" target="_blank">монитор</a>:<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class RobotSuspendResume {
static final Object monitor = new Object();
class Leg implements Runnable {
private final String name;
Leg(String name) {
this.name = name;
}
@Override
public void run() {
while(true) {
synchronized (monitor) {
step();
Thread.currentThread().suspend();
}
}
}
private void step() {
System.out.println(name);
}
}
Leg left = new Leg("LEFT");
Leg right = new Leg("RIGHT");
void run() {
new Thread(left).start();
new Thread(right).start(); // DEADLOCK!
}
public static void main(String[] args) {
new RobotSuspendResume().run();
}
}</code></pre>
</div>
Теперь нам требуется чтобы кто-то из вне вовремя будил наши потоки. Но прямая ссылка на поток не часто доступна для вызова resume() и велика вероятность того, что наши потоки так и останутся в состоянии "frozen" processes.<br />
<br />
Альтернативой методам <span style="font-family: "courier new" , "courier" , monospace;">suspend()</span> и <span style="font-family: "courier new" , "courier" , monospace;">resume()</span> являются методы <span style="font-family: "courier new" , "courier" , monospace;">wait()</span>, <span style="font-family: "courier new" , "courier" , monospace;">notify()</span> и <span style="font-family: "courier new" , "courier" , monospace;">notifyAll()</span>.<br />
<br />
Метод <span style="font-family: "courier new" , "courier" , monospace;">wait()</span> переводит поток в состояние Waiting (или Timed Waiting, если указан таймаут ожидания), а методы <span style="font-family: "courier new" , "courier" , monospace;">notify()</span> и <span style="font-family: "courier new" , "courier" , monospace;">notifyAll()</span> возвращают его в состояние Runnable.<br />
<br />
Важно понимать, что это методы не класса Thread, а класса Object который может быть легко расшарен между потоками, что позволяет избежать вышеописанных трудностей с методами suspend и resume.<br />
<br />
Теперь мы можем разделить между нашими потоками общий монитор и сделав шаг будить всех его владельцев, после чего спокойно начинать ждать пока нас кто-нибудь разбудит:<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class RobotWait {
private final Object monitor = new Object();
class Leg implements Runnable {
private final String name;
Leg(String name) {
this.name = name;
}
@Override
public void run() {
while (true) {
step();
monitor.notify();
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void step() {
System.out.println(name);
}
}
Leg left = new Leg("LEFT");
Leg right = new Leg("RIGHT");
void run() {
new Thread(left).start();
new Thread(right).start();
}
public static void main(String[] args) {
new RobotWait().run();
}
}</code></pre>
</div>
Запуск этого кода приведет к исключению:<br />
<div class="console">
<pre>LEFT
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
RIGHT
at java.lang.Object.notifyAll(Native Method)
at ru.dokwork.RobotWait$Leg.run(RobotWait.java:20)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.notifyAll(Native Method)
at ru.dokwork.RobotWait$Leg.run(RobotWait.java:20)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0</pre>
</div>
Дело в том, что прежде чем выполнить операцию notify(), notifyAll() или wait() поток должен завладеть монитором, на котором он собирается ее выполнить:<br />
<div class="code" id="code_initial">
<pre><code class="scala">while(true) {
synchronized (monitor) {
step();
monitor.notify();
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}</code></pre>
</div>
Это позволяет избежать взаимных блокировок, когда оба потока уходят в ожидание одного и того же монитора. Конкретно в нашем примере велика вероятность того, что оба потока одновременно вызовут метод notify, а затем вместе уснут, и некому будет их разбудить.<br />
<br />
Важно не забывать об этом, тк к сожалению, компилятор не может вам об этом напомнить, а вспоминать об этом в в runtime очень не приятно!<br />
<br />
С другой стороны метод wait выполняется внутри блока синхронизации и может создаться впечатление, что поток уже никогда не освободит монитор. Но на самом деле это не так. Все дело в том, что метод wait освобождает монитор, а проснувшись, поток попадает в состояние ожидания монитора, который он освободил перед сном.<br />
<br />
Раз засыпая поток освобождает монитор, то таких заснувших потоков может быть много - отсюда существование метода notifyAll() и замечание о том, что метод notify() пробуждает <b>случайный </b>из спящих потоков.<br />
<br />
Теперь наше решение выглядит куда более разумным, ведь мы избавились от пустой траты ресурсов на выполнение холостых циклов!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix0fWi0Y2NtMwg7t9eyvur2ZQxCe-wiEQ8ITeiBOtju3RAYa9QfIy3pbR6kEKrYRkCC_3-l1_DcRuXZxsN_v3Jn4o8fRKxYbVExQLFblfi-zdHanBG79w033eiEJzqznzJI29CfFAP14EQ/s1600/jwait.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEix0fWi0Y2NtMwg7t9eyvur2ZQxCe-wiEQ8ITeiBOtju3RAYa9QfIy3pbR6kEKrYRkCC_3-l1_DcRuXZxsN_v3Jn4o8fRKxYbVExQLFblfi-zdHanBG79w033eiEJzqznzJI29CfFAP14EQ/s320/jwait.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Показания VisualVM</td></tr>
</tbody></table>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMl4fzYASBKcsLr-cyte585YkJC9YaHp__jEOhfL4pcaEGqoSZz_tJeRm5vj0yXK1E3dYIQ48VPfJCKcwe7fpjLsrbgDjcIHi2yfjjBJcogGmiLeVZYck4UG1Uc0vUdCKk6-9qkI_smZEM/s1600/wait.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMl4fzYASBKcsLr-cyte585YkJC9YaHp__jEOhfL4pcaEGqoSZz_tJeRm5vj0yXK1E3dYIQ48VPfJCKcwe7fpjLsrbgDjcIHi2yfjjBJcogGmiLeVZYck4UG1Uc0vUdCKk6-9qkI_smZEM/s320/wait.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Показания System Monitor</td></tr>
</tbody></table>
<h3 style="text-align: left;">
Потокам тоже снятся страшные сны</h3>
У потока в java есть одна очень важная особенность, которую можно запросто упустить из виду. Она называется <i style="background-color: white;">spurious wakeup</i> и заключается в том, что поток может выйти из состояния ожидания без явных на то причин. Об этом можно явно прочитать в <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#wait%28long%29" target="_blank">документации</a>:<br />
<blockquote>
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup.</blockquote>
Непосредственно для нашего примера с шагающим роботом случайное пробуждение не будет иметь сколько нибудь серьезных последствий. И чтобы прочувствовать всю боль от этого эффекта, давайте несколько изменим условия задачи. Пусть наш робот научится лазать по канату. Для этого в отдельном потоке мы будем управлять движением его рук: схватить канат, отпустить канат:<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class RobotClimber {
private final Object monitor = new Object();
class Hand implements Runnable {
private final String name;
Hand(String name) {
this.name = name;
}
@Override
public void run() {
while(true) {
synchronized (monitor) {
grab();
monitor.notify();
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
release();
}
}
private void grab() {
System.out.println(name + ": GRAB");
}
private void release() {
System.out.println(name + ": RELEASE");
}
}
Hand left = new Hand("LEFT");
Hand right = new Hand("RIGHT");
void run() {
new Thread(left).start();
new Thread(right).start();
}
public static void main(String[] args) {
new RobotClimber().run();
}</code></pre>
</div>
<br />
<div class="console">
<pre>LEFT: GRAB
RIGHT: GRAB
LEFT: RELEASE
LEFT: GRAB
RIGHT: RELEASE
RIGHT: GRAB
LEFT: RELEASE
LEFT: GRAB
RIGHT: RELEASE
RIGHT: GRAB
LEFT: RELEASE
...</pre>
</div>
Теперь, из-за непреднамеренного просыпания, может случиться непоправимое! Наш робот может сорваться вниз*:<br />
<br />
<ol style="text-align: left;">
<li>Левая рука схватила канат и уснула</li>
<li>Правая рука разбудила левую и схватила канат</li>
<li>Левая рука проснулась</li>
<li>Левая рука отпустила канат</li>
<li>Неожиданно проснулась правая рука!</li>
<li>Правая рука отпустила канат</li>
<li>Крах!</li>
</ol>
<span style="font-size: x-small;">*Пример приведен чисто теоретический, для наглядной демонстрации последствий описываемой проблемы. Мне не удавалось (да я и не сильно пытался) воспроизвести описываемую последовательность событий на практике</span><br />
<br />
Чтобы это предотвратить, документация советует нам вызывать метод wait в цикле с явной проверкой необходимости проснуться:<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class RobotClimber {
private final Object monitor = new Object();
private volatile boolean currentHand = false;
class Hand implements Runnable {
private final String name;
private final boolean hand;
Hand(String name, boolean hand) {
this.name = name;
this.hand = hand;
}
@Override
public void run() {
while(true) {
synchronized (monitor) {
currentHand = !hand;
grab();
monitor.notify();
while (currentHand != hand) {
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
release();
}
}
private void grab() {
System.out.println(name + ": GRAB");
}
private void release() {
System.out.println(name + ": RELEASE");
}
}
Hand left = new Hand("LEFT", false);
Hand right = new Hand("RIGHT", true);
void run() {
new Thread(left).start();
new Thread(right).start();
}
public static void main(String[] args) {
new RobotClimber().run();
}
}</code></pre>
</div>
<br />
<h3 style="text-align: left;">
Как остановить поток?</h3>
У многих начинающих изучать потоки в java людей возникает вопрос: как в общем случае мне остановить поток? Короткий ответ: никак.<br />
<br />
Не смотря на то, что в классе Thread есть подходящий метод <span style="font-family: "courier new" , "courier" , monospace;">stop()</span>, он отмечен как устаревший и вообще предан жесточайшей анафеме!<br />
<br />
Почему? Одна из причин заключается в том, что поток может быть остановлен в момент владения монитором и проведения критических операций, в результате чего изменяемый объект будет доступен во вне в непредсказуемом состоянии.<br />
<br />
Для примера рассмотрим пример с денежными счетами и переводом средств между ними:
<br />
<div class="code" id="code_initial">
<pre><code class="scala">public class Transactions {
static class Account {
private long balance = 0;
Account(long initial) {
this.balance = initial;
}
public long getBalance() {
return balance;
}
void add(long value) {
this.balance += value;
}
void withdraw(long value) {
if (value > this.balance) {
throw new IllegalArgumentException();
}
this.balance -= value;
}
}
static class Transaction extends Thread {
private final Object context;
private final Account from;
private final Account to;
private final long value;
Transaction(Object context, Account from, Account to, long value) {
this.context = context;
this.from = from;
this.to = to;
this.value = value;
}
@Override
public void run() {
synchronized (context) {
from.withdraw(value);
to.add(value);
}
}
}
public static void main(String[] args) throws InterruptedException {
Object context = new Object();
Account first = new Account(100L);
Account second = new Account(100L);
Transaction tx_1 = new Transaction(context, first, second, 50L);
Transaction tx_2 = new Transaction(context, second, first, 150L);
tx_1.start();
// tx_1.stop(); <- подобная операция может привести систему в неконсистентное состояние
tx_1.join(); // ожидаем пополнения второго счета до 150
tx_2.start();
tx_2.join(); // ожидаем пополнения первого счета до 200</code></pre>
</div>
Метод <span style="font-family: "courier new" , "courier" , monospace;">join()</span> - это метод для ожидания завершения работы потока. Он переводит поток, <b>в</b> котором был вызван, в состояние Waiting до тех пор, пока не завершится тот поток, <b>для</b> которого этот метод был вызван.<br />
<br />
Если попытаться остановить первую транзакцию с помощью метода <span style="font-family: "courier new" , "courier" , monospace;">stop()</span> сразу после запуска, то есть вероятность того, что поток будет прерван после списания средств с первого счета, но до перевода их на второй и мы получим неприятную ситуацию с исчезновением средств в системе, что приведет к исключению во время проведения второй транзакции.<br />
<br />
Как быть, если нам очень надо сделать транзакции прерываемыми и все таки останавливать потоки? В таком случае мы должны уметь понять, что транзакцию пытаются прервать и предпринять меры по откату уже произведенных операций.<br />
<br />
Чтобы указать потоку, что его работа должна быть прервана, существует метод interrupt(). Если поток на момент вызова метода interrupt() находился в ожидании выполнения метода wait(), sleep() или join(), будет сгенерировано исключение <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/InterruptedException.html" target="_blank">InterruptedException</a>. При этом, если поток выполнял вычисления (например бегал в цикле), то эти вычисления не будут прерваны, а поток просто будет отмечен как прерванный.<br />
<div class="code">
<pre><code class="scala">public class TransactionsInterrupt {
static class Account {
private long balance = 0;
Account(long initial) {
this.balance = initial;
}
public long getBalance() {
return balance;
}
void add(long value) {
this.balance += value;
}
void withdraw(long value) {
if (value > this.balance) {
throw new IllegalArgumentException();
}
this.balance -= value;
}
}
static class Transaction extends Thread {
private final Object context;
private final Account from;
private final Account to;
private final long value;
Transaction(Object context, Account from, Account to, long value) {
this.context = context;
this.from = from;
this.to = to;
this.value = value;
}
@Override
public void run() {
synchronized (context) {
from.withdraw(value);
if (isInterrupted()) {
from.add(value);
return;
}
to.add(value);
if (isInterrupted()) {
to.withdraw(value);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object context = new Object();
Account first = new Account(100L);
Account second = new Account(100L);
Transaction tx_1 = new Transaction(context, first, second, 50L);
tx_1.start();
tx_1.interrupt();
tx_1.join();
// транзакция должна была откатиться, баланс не должен был измениться
assert first.getBalance() == 100L;
assert second.getBalance() == 100L;
}
}</code></pre>
</div>
<h3 style="text-align: left;">
Не глотайте бездумно InterruptedException!</h3>
Исключение InterruptedException таит в себе опасность. Чтобы наглядно ее продемонстрировать, добавим новое условие в старую задачу: добавим метод для остановки нашего шагающего робота. Для этого заменим бесконечный цикл на цикл с условием остановки, когда текущий поток будет прерван:<br />
<div class="code">
<pre><code class="scala">public class RobotStop {
private final Object monitor = new Object();
class Leg implements Runnable {
private final String name;
Leg(String name) {
this.name = name;
}
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
synchronized (monitor) {
step();
monitor.notify();
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void step() {
System.out.println(name);
}
}
Thread left = new Thread(new Leg("LEFT"));
Thread right = new Thread(new Leg("RIGHT"));
void run() {
left.start();
right.start();
}
void stop() throws InterruptedException {
left.interrupt();
right.interrupt();
left.join();
right.join();
}
public static void main(String[] args) throws InterruptedException {
RobotStop robot = new RobotStop();
robot.run();
Thread.sleep(1000L);
robot.stop();
}
}
</code></pre>
</div>
Здесь и ранее, допущена ошибка в обработке исключения <span style="font-family: "courier new" , "courier" , monospace;">InterruptedException</span> из метода <span style="font-family: "courier new" , "courier" , monospace;">wait()</span><span style="font-family: inherit;">. Проблема заключается в том, что при возникновении этого исключения поток не помечается как прерванный и цикл в нашем примере не будет прерван! </span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">К сожалению, исключение </span><span style="font-family: "courier new" , "courier" , monospace;">InterruptedException</span> создано как checked, и должно либо фигурировать в сигнатуре метода <span style="font-family: "courier new" , "courier" , monospace;">run()</span>, либо явно обработано внутри метода. Перехватив это исключение мы не должны его просто игнорировать, нам необходимо самостоятельно отметить поток как прерванный:<br />
<div class="code">
<pre><code class="scala">try {
monitor.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}</code></pre>
</div>
<h3 style="text-align: left;">
Так какие же бывают у потока состояния?</h3>
В статье неоднократно упоминались состояния потока и условия перехода из одного состояния в другое. В конце статьи мне бы хотелось подвести итог этому вопросу, приведя наглядную диаграмму правил перехода:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5N0dDzW9d0QSVm-WJG6c7MJ1YKmFdL-2OZJbP_6EFEpLqwkuWjQgUT-5PFQstqVMZHnplLNAf9nLFSoQ_g0qGdhtq9WElT2WO43o4gFzO0Htm9JY9qMjChnQyb7ZW4bCm6rGrs663Y76I/s1600/thread_states.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="377" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5N0dDzW9d0QSVm-WJG6c7MJ1YKmFdL-2OZJbP_6EFEpLqwkuWjQgUT-5PFQstqVMZHnplLNAf9nLFSoQ_g0qGdhtq9WElT2WO43o4gFzO0Htm9JY9qMjChnQyb7ZW4bCm6rGrs663Y76I/s640/thread_states.png" width="640" /></a></div>
<br />
<i><span style="font-size: x-small;">*данная диаграмма не отражает условий смены состояний потока через вызовы методов класса <span style="font-family: "courier new" , "courier" , monospace;"><a href="http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/LockSupport.html" target="_blank">java.util.concurrent.locks.LockSupport</a>.</span></span></i><br />
<br />
<br />
[1] <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html" target="_blank">Java Thread Primitive Deprecation</a><br />
[2] <a href="https://blogs.oracle.com/vmrobot/entry/%D1%81%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D1%8B%D0%B9_%D0%B2%D1%8B%D1%85%D0%BE%D0%B4_%D0%B8%D0%B7_object_wait" target="_blank">"Случайный" выход из Object.wait()</a><br />
[3] <a href="https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.State.html" target="_blank">Enum Thread.State</a><br />
[4] <a href="https://habrahabr.ru/post/143237/" target="_blank">А как же всё-таки работает многопоточность?</a></div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com2tag:blogger.com,1999:blog-7407396207758383724.post-47652849571288345432016-08-26T18:36:00.000+03:002016-09-15T16:58:20.572+03:00Навигация по истории в терминале<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiikAgjLSjrRTeS8D9Rix4IvlM0y7EVmxHOMNXN9Q7G0yfT8-pDKO6TYo5ttZIMf_V0OlBHKlmK6whZBbpGLldWgE55loyDBexc3i9SPiDq3KgWKqFH8hB7NinkM43kNZwn8R-U5gpVRaJR/s1600/example.gif" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiikAgjLSjrRTeS8D9Rix4IvlM0y7EVmxHOMNXN9Q7G0yfT8-pDKO6TYo5ttZIMf_V0OlBHKlmK6whZBbpGLldWgE55loyDBexc3i9SPiDq3KgWKqFH8hB7NinkM43kNZwn8R-U5gpVRaJR/s1600/example.gif" /></a></td><td>Маленький хак упрощающий навигацию в Linux. Размещаем инструкции в ~/.bashrc:<br />
<div class="text">
<pre>bind '"\e[A": history-search-backward'
bind '"\e[B": history-search-forward'</pre>
</div>
и навигация по истории учитывает набранные до курсора символы.
</td></tr>
</tbody></table>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com1tag:blogger.com,1999:blog-7407396207758383724.post-21202285316616932532016-05-11T19:16:00.000+03:002016-05-11T19:21:49.926+03:00Что надо знать про коллекции в Scala<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimRcjKg0GZ08Xsj1JXVsLsv448UFO9peDEnM293tXTKTB2E1SjHMIET03-e9gkmIV4kH-06CPwqKnciDxyj03lEr-rkkuFg3-CFAg91Fo6VZU6VIhFjDWxNxb9DJSb7HFKkRVxQrBTB899/s1600/scala_collections.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimRcjKg0GZ08Xsj1JXVsLsv448UFO9peDEnM293tXTKTB2E1SjHMIET03-e9gkmIV4kH-06CPwqKnciDxyj03lEr-rkkuFg3-CFAg91Fo6VZU6VIhFjDWxNxb9DJSb7HFKkRVxQrBTB899/s200/scala_collections.png" width="123" /></a></div>
</td><td>Библиотека коллекции в scala - одна из самых сильных особенностей языка, но в то же время и одна самых трудных его частей. Данная статья представляет собой конспект особенностей и нюансов коллекций в scala.<br />
<br />
<b>Содержание:</b>
<br />
<ul>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#review">Обзор</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#streams">Streams</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#view">Ленивые коллекции View</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#ranges">Ranges</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#performance">Производительность коллекций</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#methods">Конспект по методам коллекций</a></li>
<li><a href="http://www.dokwork.ru/2016/05/scalacollection.html#conclusion">Заключение</a></li>
</ul>
<br /></td></tr>
</tbody></table>
<a name='more'></a><div style="text-align: left;">
<br /></div>
<h2 id="review" style="text-align: left;">
Обзор</h2>
<div>
<br /></div>
В scala коллекции делятся на изменяемые и неизменяемые, что положительно выделяет их на фоне стандартной библиотеки коллекций в Java.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgixqso-Xv2LiWiW7-K3wFdVALkkSRlx7Oo-MJZchWP3YiWkbcaK-JfP_-jJFkpoKsmRzd4upxTXuO0NaPx42hHD0jJtNbgiVRgWdF9U9d08Zzeqz3gQAoEDlWd5XWHrRMbcBm9u7vYJHF0/s1600/collections.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgixqso-Xv2LiWiW7-K3wFdVALkkSRlx7Oo-MJZchWP3YiWkbcaK-JfP_-jJFkpoKsmRzd4upxTXuO0NaPx42hHD0jJtNbgiVRgWdF9U9d08Zzeqz3gQAoEDlWd5XWHrRMbcBm9u7vYJHF0/s320/collections.png" width="320" /></a>
</td>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKmrgMXGcS3uuXzXeo4yFItPKo0oG0DVpCy0gZUoBZSikfqg5g8AAPBe9EDq0q_ke-VTdDTyFGRTlrR9pmjEuuqLRNUcGLaH8wRgNvKhv03baDVXNMKXiZUWyS7p64mbYpg5W-Sj1GcJFF/s1600/java-colection-Interfaces.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="153" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKmrgMXGcS3uuXzXeo4yFItPKo0oG0DVpCy0gZUoBZSikfqg5g8AAPBe9EDq0q_ke-VTdDTyFGRTlrR9pmjEuuqLRNUcGLaH8wRgNvKhv03baDVXNMKXiZUWyS7p64mbYpg5W-Sj1GcJFF/s320/java-colection-Interfaces.png" width="320" /></a></td>
</tr>
<tr>
<td class="tr-caption" style="text-align: center;">Интерфейсы коллекций в scala.collection</td>
<td class="tr-caption" style="text-align: center;">Интерфейсы коллекций в Java</td></tr>
</tbody></table>
<br />
<h4 style="text-align: left;">
Неизменяемые коллекции</h4>
Неизменяемые коллекции находятся в пакете <span style="font-family: "courier new" , "courier" , monospace;"><a href="http://www.scala-lang.org/api/current/#scala.collection.immutable.package" target="_blank">scala.collection.immutable</a></span>. Коллекции в данном пакете не реализуют методов изменяющих их внутреннее состояние, вместо этого все методы, добавляющие элементы, удаляющие их или иначе изменяющие коллекцию, создают новый экземпляр оригинальной коллекции содержащий соответствующие изменения.
<br />
<div class="console">
<pre>scala> List(1, 2) ++ List(3, 4)
res0: List[Int] = List(1, 2, 3, 4)</pre>
</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE37ft8gkc1PP4YbbIf7rJkTGyxmylJILgdhqyC3HB0_8N1N1k3PQaunCB3xl1gPokuGUOyIEVCsbCmVum3qlwypkMxjWg8bdeT_oK2LLxHEMkxj8it5weVMYKcZP3U90iJG4EwHjfFC_S/s1600/collections.immutable.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="291" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE37ft8gkc1PP4YbbIf7rJkTGyxmylJILgdhqyC3HB0_8N1N1k3PQaunCB3xl1gPokuGUOyIEVCsbCmVum3qlwypkMxjWg8bdeT_oK2LLxHEMkxj8it5weVMYKcZP3U90iJG4EwHjfFC_S/s400/collections.immutable.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Неизменяемые коллекции в scala.collection.immutable</td></tr>
</tbody></table>
<h4 style="text-align: left;">
Изменяемые коллекции</h4>
Изменяемые коллекции находятся в пакете <span style="font-family: "courier new" , "courier" , monospace;"><a href="http://www.scala-lang.org/api/current/#scala.collection.mutable.package" target="_blank">scala.collection.mutable</a></span> и реализуют общие с неизменяемыми коллекциями интерфейсы из пакета scala.collection. Т.к. интерфейсы у mutable и immutable коллекций одни, все методы неизменяемых коллекций доступны и для изменяемых коллекций, но последние, в дополнение, содержат методы для изменения своего внутреннего состояния.
<br />
<div class="console">
<pre>scala> val buffer = collection.mutable.Buffer(1,2,3)
buffer: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3)
scala> buffer.append(4)
scala> buffer
res6: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3, 4)</pre>
</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhELJSlcwAwwvwQpt-nah6v3K2ceMDVcWrXzgdU47LGDgANMDGAuCuTqDPna8C1W87EiPp56wVYTICk0J-UUGSytsT8DIIUAzZQ-Z7Mzo38N6aBqSb9PU0uMSpneVRviPdNxV7sobS7GIAZ/s1600/collections.mutable.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="251" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhELJSlcwAwwvwQpt-nah6v3K2ceMDVcWrXzgdU47LGDgANMDGAuCuTqDPna8C1W87EiPp56wVYTICk0J-UUGSytsT8DIIUAzZQ-Z7Mzo38N6aBqSb9PU0uMSpneVRviPdNxV7sobS7GIAZ/s400/collections.mutable.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Изменяемые коллекции в scala.collection.mutable</td></tr>
</tbody></table>
<br />
Вообще, на этом разделение коллекций по пакетам не заканчивается, т.к. существует еще один пакет <span style="font-family: "courier new" , "courier" , monospace;"><a href="http://www.scala-lang.org/api/current/#scala.collection.generic.package" target="_blank">scala.collelction.generic</a></span>, который содержит более детальные интерфейсы для реализации собственных библиотек с коллекциями, но его я затрагивать не стану.<br />
<br />
Вместо этого отмечу, что в силу популярности коллекций, чтобы не импортировать нужные пакеты всякий раз, когда вам понадобится список, в scala предусмотрели псевдонимы для наиболее популярных коллекций прямо в пакете scala, который негласно импортируется всегда. Среди них: <span style="font-family: "courier new" , "courier" , monospace;">Traversable</span>, <span style="font-family: "courier new" , "courier" , monospace;">Iterable</span>, <span style="font-family: "courier new" , "courier" , monospace;">Seq</span>, <span style="font-family: "courier new" , "courier" , monospace;">IndexedSeq</span>, <span style="font-family: "courier new" , "courier" , monospace;">Iterator</span>, <span style="font-family: "courier new" , "courier" , monospace;">Stream</span>, <span style="font-family: "courier new" , "courier" , monospace;">Vector</span>, <span style="font-family: "courier new" , "courier" , monospace;">List</span>, <span style="font-family: "courier new" , "courier" , monospace;">StringBuilder</span> и <span style="font-family: "courier new" , "courier" , monospace;">Range</span>.<br />
<br />
<h4 style="text-align: left;">
Java коллекции</h4>
Так как scala - язык работающий поверх JVM, ему доступно все богатство библиотек для Java, и коллекции здесь не исключение. Можно импортировать и создавать коллекции из пакета java.util точно также как и любые другие классы. Но java коллекции не реализуют интерфейсов scala коллекций, что может затруднить работу в случае необходимости передать scala-коллекцию в java-код или наоборот, java-коллекцию в scala функцию. К счастью в scala реализованы специальные конверторы, позволяющие неявно преобразовывать java-коллекции в их scala аналоги совершенно прозрачно. Для этого достаточно выполнить импорт объекта JavaConverters и всех его методов:<br />
<div class="code">
<pre><code class="java">import collection.JavaConverters._
val buffer: mutable.Buffer[Int] = new java.util.ArrayList[Int]().asScala
val list: java.util.List[Int] = new mutable.ArrayBuffer().asJava</code></pre>
</div>
Таблица соответстия между коллекциями выглядит следующим образом:<br />
<div class="code">
<pre>Iterator <=> java.util.Iterator
Iterator <=> java.util.Enumeration
Iterable <=> java.lang.Iterable
Iterable <=> java.util.Collection
mutable.Buffer <=> java.util.List
mutable.Set <=> java.util.Set
mutable.Map <=> java.util.Map
mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap</pre>
</div>
<div style="text-align: left;">
<br /></div>
<h4 style="text-align: left;">
Преобразования коллекций</h4>
Для удобства использования, для каждой коллекции определены методы для преобразования в коллекцию другого типа вида to{Название коллекции}:
<br />
<div class="code">
<pre><code class="scala">def toArray : Array[A]
def toArray [B >: A] (implicit arg0: ClassManifest[B]) : Array[B]
def toBuffer [B >: A] : Buffer[B]
def toIndexedSeq [B >: A] : IndexedSeq[B]
def toIterable : Iterable[A]
def toIterator : Iterator[A]
def toList : List[A]
def toMap [T, U] (implicit ev: <: :="" def="" map="" seq="" toseq="" toset="" u="">: A] : Set[B]
def toStream : Stream[A]
def toString () : String
def toTraversable : Traversable[A]</:></code></pre>
</div>
<h2 id="streams" style="text-align: left;">
Streams</h2>
<br />
Среди прочих коллекций в scala, хочется отметить <span style="font-family: "courier new" , "courier" , monospace;">scala.collection.immutable.Stream</span>. Это рекуррентно вычисляемая коллекция бесконечной длинны. Ее проще объяснить на примере, чем описывать словами. Типичным примером для демонстрации Stream в scala является задача вычисления N-го члена последовательности Фибоначи:
<br />
<div class="code">
<pre><code class="scala">def fibFrom(a: Int, b: Int): Stream[Int] = a #:: fibFrom(b, a + b)
val fibs: Stream[Int] = fibFrom(1, 1).take(7)
fibs.toList // = List(1, 1, 2, 3, 5, 8, 13)</code></pre>
</div>
Здесь следует отметить важный нюанс: stream кеширует вычисленный ранее результат и возвращает значение из кэша вместо повторного вычисления.<br />
<br />
Теперь, когда вы знаем о том, что коллекции в scala не всегда бывают конечными, вас не должен удивить метод <span style="font-family: "courier new" , "courier" , monospace;">hasDefiniteSize</span>, возвращающий true, если коллекция конечного размера.<br />
<br />
<h2 id="view" style="text-align: left;">
Ленивые коллекции View</h2>
<div>
<br /></div>
Рассмотрим следующую простую задачу: пусть у нас есть случайный набор целочисленных значений, наша задача найти среди них отрицательные и возвести в квадрат. Благодаря богатому набору методов, решить эту задачу не составит труда:
<br />
<div class="code">
<pre><code class="scala">def solution(numbers: Seq[Int]): Seq[Int] = numbers filter (_ < 0) map (x => x * x)</code></pre>
</div>
Но у внимательного и ответственного разработчика должен возникнуть вопрос об эффективности подобного решения. Ведь каждый из методов map и filter порождает новую коллекцию?!<br />
<br />
Да, это действительно так. В результате фильтрации будет создана новая коллекция, копирующая(в общем случае) отрицательные элементы из предыдущей. И после выполнения операции map клонирование коллекции повторится. Таким образом, чем больше операций в цепочке, тем сильнее потери в производительности!<br />
<br />
Но не спешите кидать камни в, местами заслуживающую это, Scala. Приберегите их для <span class="pointer" onclick="$('#properties').toggle();">свойств объектов</span> :)<br />
<div id="properties" style="display: none;">
<br />
<i>Проблема в том, что объявив свойство как var property вы не можете переопределить для него сеттер или геттер, для этого вам необходимо завести это свойство через явное описание того и другого: </i>
<br />
<div class="code">
<pre><code class="scala">private var _property
def property = _property
def property_=(value: T) = _property = value</code></pre>
</div>
</div>
<br />
Специально для таких цепочек преобразований в scala предусмотрены ленивые коллекции называемые View. Это что-то вроде виртуальных коллекций, которые не содержат элементов, а лишь ссылаются на оригинальную коллекцию. Суть их очень проста, вместо создания новой коллекции после каждой операции, они применяют сразу всю цепочку преобразований к каждому элементу оригинальной коллекции, благодаря чему цепочка преобразований выполняется за один проход. Для преобразования оригинальной коллекции во view есть соответствующий метод view:<br />
<div class="code">
<pre><code class="scala">def solution(numbers: Seq[Int]): Seq[Int] = numbers.view filter (_ < 0) map (x => x * x)</code></pre>
</div>
Обратите внимание на то, что по факту такой метод возвращает не просто Seq, а именно SeqView. Т.е. инструкции по преобразованию оригинальной коллекции, а не готовый результат. Если в метод передать изменяемую коллекцию и затем изменить ее элементы, то это повлечет перемены и в результате работы метода:<br />
<div class="console">
<pre>scala> def solution(numbers: Seq[Int]) = numbers.view filter (_ < 0) map (x => x * x)
solution: (numbers: Seq[Int])scala.collection.SeqView[Int,Seq[_]]
scala> val arg = Array(-1, 2, -3)
arg: Array[Int] = Array(-1, 2, -3)
scala> val result = solution(arg)
result: scala.collection.SeqView[Int,Seq[_]] = SeqViewFM(...)
scala> result.toList
res8: List[Int] = List(1, 9)
scala> arg(0) = 4
scala> result.toList
res10: List[Int] = List(9)
</pre>
</div>
Преобразование виртуальной коллекции в реальную выполняется либо одним из преобразующих методов из серии to{Название коллекции}, либо методом force:<br />
<div class="console">
<pre>scala> def solution(numbers: Seq[Int]) = numbers.view filter (_ < 0) map (x => x * x) force
solution: (numbers: Seq[Int])Seq[Int]
scala> val arg = Array(-1, 2, -3)
arg: Array[Int] = Array(-1, 2, -3)
scala> val result = solution(arg)
result: Seq[Int] = ArrayBuffer(1, 9)
scala> arg(0) = 4
scala> result.toList
res12: List[Int] = List(1, 9)</pre>
</div>
<br />
<h2 id="ranges" style="text-align: left;">
Ranges</h2>
<br />
Интервалы в scala - это упорядоченные последовательности целых чисел из заданного диапазона и изменяющихся с некоторым шагом:<br />
<div class="code">
<pre><code class="scala">new Range(0, 6, 2) // Создает диапазон чисел [0, 6) с шагом 2, т.е. 0, 2, 4</code></pre>
</div>
Диапазон чисел доступных для использования в Range соответствует диапазону типа Int. Для удобства использования, в классе RichInt определены методы <a href="http://www.scala-lang.org/api/current/index.html#scala.runtime.RichInt@to(end:Int,step:Int):scala.collection.immutable.Range.Inclusive" target="_blank">to</a> и <a href="http://www.scala-lang.org/api/current/index.html#scala.runtime.RichInt@until(end:Int,step:Int):scala.collection.immutable.Range" target="_blank">until</a>. Первый создает диапазон, включающий обе границы, второй - включает только первую границу диапазона. Оба метода возвращают Inclusive - наследника класса Range, несколько его расширяющего. В частности привносящего метод by, который позволяет указать шаг между значениями диапазона:<br />
<div class="console">
<pre>scala> 0 until 6
res0: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4, 5)
scala> 0 to 6
res1: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5, 6)
scala> 0 to 6 by 2
res2: scala.collection.immutable.Range = Range(0, 2, 4, 6)</pre>
</div>
Реализация Range не содержит всех элементов диапазона, а хранит только его границы и шаг, что делает использование этих коллекций очень дешевым.<br />
<br />
<br />
<h2 id="performance" style="text-align: left;">
Производительность коллекций</h2>
<div>
<br /></div>
<div>
<style>
.perf tr:hover {
background-color: #F7F7F7;
}
</style>
<br />
<h4 style="text-align: left;">
Обозначения</h4>
<table cellpadding="5pt" rules="all" style="border: 1px solid rgb(221, 221, 221); font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; width: 580px;"><tbody>
<tr><td style="padding: 0 5pt;"><strong>c</strong></td><td>Операция выполняет за постоянное время.</td></tr>
<tr><td style="padding: 0 5pt;"><strong>~c</strong></td><td>Операция занимает эффективное постоянное время, но может зависеть от некоторых условий, таких как длинна коллекции или распределение хеш-ключей.</td></tr>
<tr><td style="padding: 0 5pt;"><strong>c*</strong></td><td>Операция занимает в общем случае константное время, но некоторые вызовы могут выполняться дольше. </td></tr>
<tr><td style="padding: 0 5pt;"><strong>log n</strong></td><td>Время выполнения операции пропорционально логарифму от размера коллекции.</td></tr>
<tr><td style="padding: 0 5pt;"><strong>n</strong></td><td>Линейная операция, время выполнения которой пропорционально размеру коллекции.</td></tr>
<tr><td style="padding: 0 5pt;"><strong>-</strong></td><td>Операция не поддерживается.</td></tr>
</tbody></table>
<br />
<h4 style="text-align: left;">
Последовательности</h4>
<table class="perf" cellpadding="5pt" rules="all" style="border: 1px solid rgb(221 , 221 , 221); font-family: "helvetica neue" , "helvetica" , "arial" , sans-serif; text-align: center; width: 580px;">
<thead>
<tr><th></th><th>head</th><th>tail</th><th>apply</th><th>update</th><th>prepend</th><th>append</th><th>insert</th></tr>
</thead><tbody>
<tr style="text-align: left;"><td><strong>immutable</strong></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>List</code></td><td>c</td><td>c</td><td>n</td><td>n</td><td>c</td><td>n</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Stream</code></td><td>c</td><td>c</td><td>n</td><td>n</td><td>c</td><td>n</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Vector</code></td><td>~c</td><td>~c</td><td>~c</td><td>~c</td><td>~c</td><td>~c</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Stack</code></td><td>c</td><td>c</td><td>n</td><td>n</td><td>c</td><td>c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Queue</code></td><td>c*</td><td>c*</td><td>n</td><td>n</td><td>n</td><td>c</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Range</code></td><td>c</td><td>c</td><td>c</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>String</code></td><td>c</td><td>n</td><td>c</td><td>n</td><td>n</td><td>n</td><td>-</td></tr>
<tr style="text-align: left;"><td><strong>mutable</strong></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>ArrayBuffer</code></td><td>c</td><td>n</td><td>c</td><td>c</td><td>n</td><td>c*</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>ListBuffer</code></td><td>c</td><td>n</td><td>n</td><td>n</td><td>c</td><td>c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>StringBuilder</code></td><td>c</td><td>n</td><td>c</td><td>c</td><td>n</td><td>c*</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>MutableList</code></td><td>c</td><td>n</td><td>n</td><td>n</td><td>c</td><td>c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Queue</code></td><td>c</td><td>n</td><td>n</td><td>n</td><td>c</td><td>c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>ArraySeq</code></td><td>c</td><td>n</td><td>c</td><td>c</td><td>-</td><td>-</td><td>-</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Stack</code></td><td>c</td><td>n</td><td>n</td><td>n</td><td>c</td><td>n</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>ArrayStack</code></td><td>c</td><td>n</td><td>c</td><td>c</td><td>c*</td><td>n</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>Array</code></td><td>c</td><td>n</td><td>c</td><td>c</td><td>-</td><td>-</td><td>-</td></tr>
</tbody></table>
</div>
<div>
<br /></div>
<h4 style="text-align: left;">
Множества и ассоциативные массивы</h4>
<div>
<table class="perf" cellpadding="5pt" rules="all" style="border: 1px solid rgb(221 , 221 , 221); font-family: "helvetica neue" , "helvetica" , "arial" , sans-serif; text-align: center; width: 580px;"><thead>
<tr><th></th><th>lookup</th><th>add</th><th>remove</th><th>min</th></tr>
</thead><tbody>
<tr style="text-align: left;"><td><strong>immutable</strong></td><td></td><td></td><td></td><td></td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>HashSet</code>/<code>HashMap</code></td><td>~c</td><td>~c</td><td>~c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>TreeSet</code>/<code>TreeMap</code></td><td>log n</td><td>log n</td><td>log n</td><td>log n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>BitSet</code></td><td>c</td><td>n</td><td>n</td><td>~c</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>ListMap</code></td><td>n</td><td>n</td><td>n</td><td>n</td></tr>
<tr style="text-align: left;"><td><strong>mutable</strong></td><td></td><td></td><td></td><td></td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>HashSet</code>/<code>HashMap</code></td><td>~c</td><td>~c</td><td>~c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>WeakHashMap</code></td><td>~c</td><td>~c</td><td>~c</td><td>n</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>BitSet</code></td><td>c</td><td>c*</td><td>c</td><td>~c</td></tr>
<tr><td style="padding-left: 15pt; text-align: left;"><code>TreeSet</code></td><td>log n</td><td>log n</td><td>log n</td><td>log n</td></tr>
</tbody></table>
</div>
<br />
<br />
<br />
<h2 id="methods" style="text-align: left;">
Конспект по методам коллекций</h2>
<br />
Коллекции в scala имеют огромный список методов для выполнения всевозможных операций как над самими коллекциями, так и над элементами. Лучшим источником информации о всевозможных методах безусловно является <a href="http://www.scala-lang.org/api/current/" target="_blank">документация</a>. Но проблема в том, что методов слишком много и не понятно с чего начать. Вот как раз начать, я советую со <a href="https://twitter.github.io/scala_school/ru/collections.html#map" target="_blank">статьи из цикла Scala school</a> от twiter, посвященной функциональным комбинаторам. Она содержит описание самых распространенных операций. Еще одним хорошим сводным источником информации могу рекомендовать <a href="https://books.google.ru/books?id=mR3RAAAAQBAJ&pg=PA184&lpg=PA184&dq=scala+%D0%B4%D0%BB%D1%8F+%D0%BD%D0%B5%D1%82%D0%B5%D1%80%D0%BF%D0%B5%D0%BB%D0%B8%D0%B2%D1%8B%D1%85+%D0%B3%D0%BB%D0%B0%D0%B2%D0%B0+13" target="_blank">13 главу из книги Scala для нетерпеливых</a>. Здесь я позволю себе привести сводную таблицу по методам из этой книги:
<br />
<br />
<table><tbody>
<tr>
<td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirb65m7kvs7FL40cw3AbvN9q-QIGWGSPa_pAW9xImgn6XEH1mRlqW2ajJoU8YrCwXCHrJZFl1Tp-xx2NTogFSf3Qjq19zdMvH5JNOOZ3WEjAgy7XonEUllRrcmf1-kV2bsCUJmVTC1Y1lC/s1600/page1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirb65m7kvs7FL40cw3AbvN9q-QIGWGSPa_pAW9xImgn6XEH1mRlqW2ajJoU8YrCwXCHrJZFl1Tp-xx2NTogFSf3Qjq19zdMvH5JNOOZ3WEjAgy7XonEUllRrcmf1-kV2bsCUJmVTC1Y1lC/s320/page1.png" width="218" /></a></td>
<td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcCKKklWT45C-0eiiIV_mXjB06v_WpXTpf-qhi1q6e8Z4_F_h6Z-ZXoq1WAbsS4Z5XvKXNB-B33aFehyphenhyphenqVDvrF_KXoZJrfc0JJpI18WL9_pvM5Zl9YNyqTHphuL9bBGKXbj66PlAsYoyKX/s1600/page2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcCKKklWT45C-0eiiIV_mXjB06v_WpXTpf-qhi1q6e8Z4_F_h6Z-ZXoq1WAbsS4Z5XvKXNB-B33aFehyphenhyphenqVDvrF_KXoZJrfc0JJpI18WL9_pvM5Zl9YNyqTHphuL9bBGKXbj66PlAsYoyKX/s320/page2.png" width="212" /></a></td>
</tr>
</tbody></table>
<br />
<h4 style="text-align: left;">
Замечание к вопросу Scala collections vs Java streams</h4>
<br />
Стремление авторов библиотеки коллекций в Scala понятно - желание быть последовательными в дизайне и дать разработчикам гибкий и удобный инструмент для преобразования коллекций. Но на мой взгляд цена такого решения слишком высока. Простота вызова функциональных комбинаторов может посеять множество трудно уловимых потерь в производительности. Любой их вызов должен быть тщательно продуман и взвешен.<br />
<br />
В то же время, решение введения новой сущности в лице Streams в Java хоть и вносит некоторую избыточность и отстает по гибкости и функциональности от аналогов в scala, все-таки позволяет избежать случайного падения производительности, что в условиях работы в больших коллективах и с большой кодовой базой является серьезным преимуществом.
<br />
<br />
<br />
<h2 id="conclusion" style="text-align: left;">
Заключение</h2>
<div>
<br /></div>
<div>
Коллекции в scala - крайне мощный инструмент в руках внимательного разработчика. Сложность и обилие методов требует некоторого времени на их понимание и наработку навыков применения, но польза от потраченного времени не заставит себя ждать. Главное не поддаваться на кажущуюся простоту коллекций и внимательно следить за тем, что вы делаете и к чему это приводит.</div>
<div>
<br /></div>
</div>Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-18580492510007000792016-03-16T17:49:00.003+03:002016-04-10T18:34:45.505+03:00Как обрезать поля в pdf файле<div dir="ltr" style="text-align: left;" trbidi="on">
Для обрезки полей в Linux есть утилита pdfcrop. Эта утилита является частью пакета texlive-extra-utils:<br />
<div class="console">
# apt-get install texlive-extra-utils
<br />
$ pdfcrop --clip --margin 5 исходный_файл.pdf целевой_файл.pdf
</div>
<br /></div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-80613365716990452732016-02-20T20:40:00.000+03:002016-03-16T17:51:46.249+03:00Убираем все лишнее при просмотре сайта в Chrome<div dir="ltr" style="text-align: left;" trbidi="on">
<table class="tr-caption-container"><tbody>
<tr>
<td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU7tMQ_JLyMv37OyqM-B9RoBZ8fPnMclrcP-mjyfhyphenhyphen4ZG790vVvOIFNfnQ6RMHaAGkdtrfQWXm4gnHkIAdC9uPnsZMmskoz4HQUxw-GdESKyH7vkQPiDm9dy0mKue8ClEmPfAVK6me7Gzf/s1600/before.png" imageanchor="1">
<img border="0" height="50" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiU7tMQ_JLyMv37OyqM-B9RoBZ8fPnMclrcP-mjyfhyphenhyphen4ZG790vVvOIFNfnQ6RMHaAGkdtrfQWXm4gnHkIAdC9uPnsZMmskoz4HQUxw-GdESKyH7vkQPiDm9dy0mKue8ClEmPfAVK6me7Gzf/s200/before.png" />
</a></td>
<td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglR-SGB8nTzaaaUWtGCMt5H7tHLEy2GewBNNztl6zYU-DBSSPi6t7RRmrzcAPsgzO00SvE1sL0pc3VlvkFM-e51FvwVlky4BNJd3xlmhdyarxdoGhANWNgWYicyStkETaBsPUl6N5kNUk6/s1600/after.png" imageanchor="1">
<img border="0" height="50" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglR-SGB8nTzaaaUWtGCMt5H7tHLEy2GewBNNztl6zYU-DBSSPi6t7RRmrzcAPsgzO00SvE1sL0pc3VlvkFM-e51FvwVlky4BNJd3xlmhdyarxdoGhANWNgWYicyStkETaBsPUl6N5kNUk6/s200/after.png" /></a></td>
<td style="padding-left: 20pt;">Если размер вашего монитора заставляет экономить каждый пиксель и по каким-то причинам режим Full screen не устраивает, в Chrome можно открыть текущую страницу в отдельном окне без табов и панели закладок!</td>
</tr>
<tr>
<td class="tr-caption" style="text-align: center;">До</td>
<td class="tr-caption" style="text-align: center;">После</td>
</tr>
</tbody></table>
<br />
<a name='more'></a><br />
<ol style="text-align: left;">
<li><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2yZHGO5j5cQJx6kNWXetvsm1S9tkcIlrEjYgun79ok3BYaAjegnwaGqshYmyIumlyLqScEPN739rLC_NHMfPQNl4nWW504OLN2BSf6szi7dXa67t9YGOZbzzEyJytawSMCmVu6HRxnOhf/s1600/bookmark.png" target="_blank">Добавляем новую закладку</a>
</li>
<li>Оставляем имя пустым(или выбираем удобное название), а в качестве адреса вставляем скрипт:
<div class="text">
javascript:window.open(location.href, "detab", "toolbar=0"); window.close()
</div>
</li>
<li>PROFIT!<br />
</li>
</ol>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-43067941583151777902016-01-17T18:00:00.001+03:002016-04-02T15:22:58.185+03:00Как отучить Sublime сжирать CPU<div dir="ltr" style="text-align: left;" trbidi="on">
Если <a href="http://www.dokwork.ru/2015/07/sublime.html">Sublime</a> стала кушать много CPU просто отключите индексирование файлов: <span style="font-family: "courier new" , "courier" , monospace;">"index_files": false</span><br />
<div class="text">
Откройте конфигурационный JSON файл <span style="font-family: "courier new" , "courier" , monospace;">Preferences -> Settings-User</span> и добавьте в него указанную инструкцию.
</div>
<br />
Ссылки по теме:
<a href="https://forum.sublimetext.com/t/why-is-sublime-using-significant-cpu-when-idle/11113/14">Why is Sublime using significant CPU when idle?</a>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-50037159667385956822015-08-23T18:37:00.000+03:002015-08-23T18:44:24.849+03:00Java со вкусом огурчика<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsgkFXqzlPdppPSNAZIlFtkyE0NNtTea0HffWpzF63hhQ9G946HYFcw3Z7SaPlBRtpACd2NDtmS0-ilXrz3N0lEu0Oq3nk5Km9okphd5oHr9sBWKOmNAoL9fIGlzvEhJv_SnWPD-2WSlnV/s1600/cucumber.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="142" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsgkFXqzlPdppPSNAZIlFtkyE0NNtTea0HffWpzF63hhQ9G946HYFcw3Z7SaPlBRtpACd2NDtmS0-ilXrz3N0lEu0Oq3nk5Km9okphd5oHr9sBWKOmNAoL9fIGlzvEhJv_SnWPD-2WSlnV/s200/cucumber.png" width="200" /></a></div>
</td><td>Роль модульного тестирования тяжело переоценить, но теория тестирования не стоит на месте. Еще не все успели привыкнуть к хипстерскому понятию TDD, как на всех углах звучит очередное трех-буквенное сокращение BDD. Исчерпывающее описание того, что же такое BDD, можно найти в статье <a href="http://agilerussia.ru/practices/introducing-bdd/" target="_blank">Введение в BDD</a>. В данной статье речь пойдет о фреймворке <a href="https://cucumber.io/" target="_blank">cucumber</a>, позволяющем наглядно воплотить в жизнь те идеи, которые заложены в тестировании через поведение.</td></tr>
</tbody></table>
<a name='more'></a><br />
<h3 style="text-align: left;">
Cucumber</h3>
Cucumber - библиотека для тестирования, которая предлагает описывать сценарии тестирования на естественном языке в обычном текстовом файле с расширением .feature:<br />
<div class="text">
<pre>Feature: Calculator
Scenario Outline: Sum of the two numbers
Given two numbers <a> and <b>
When we try to find sum of our numbers
Then result should be <result>
Examples:
| a | b | result |
| 3 | 2 | 5 |
</pre>
</div>
<br />
И связывать их с реализующим их кодом:<br />
<div class="code">
<pre><code class="java">public class CalculatorSteps {
private Calculator calc;
double a;
double b;
double result;
@Given("^two numbers (\\d) and (\\d)")
public void given(double a, double b) {
this.a = a;
this.b = b;
this.calc = new Calculator();
}
@When("^we try to find sum of our numbers")
public void when() {
result = calc.sum(a, b);
}
@Then("^result should be (\\d)")
public void then(double res) {
Assert.assertEquals(res, result, 0.0001);
}
}</code></pre>
</div>
<span class="pointer" onclick="$('#calculator').toggle();">Код класса Calculator</span>
<br />
<div class="code" id="calculator" style="display: none;">
<pre><code class="java">public class Calculator {
public double sum(double a, double b) {
return a + b;
}
public double pow(double a, double b) {
return a * b;
}
}</code></pre>
</div>
<br />
Основой описания сценария тестирования являются <b>шаги</b>, такие как Given, When, Then. Каждому шагу соответствует аннотация, которая с помощью регулярного выражения связывает метод, над которым объявлена, со строкой в текстовом описании сценария.<br />
<br />
Шаги тестирования группируются в сценарии (Scenario), которые в свою очередь описывают некоторую функциональность (Feature).</div>
<h3 style="text-align: left;">
Структура проекта </h3>
<div class="code">
<pre>src
├── main
│ ├── java
│ │ └── ru
│ │ └── dokwork
│ │ └── cucumber
│ │ └── Calculator.java
│ └── resources
└── test
├── java
│ └── ru
│ └── dokwork
│ └── cucumber
│ ├── CalculatorSteps.java
│ └── CucumberTestRunner.java
└── resources
└── ru
└── dokwork
└── cucumber
└── calculator.feature
</pre>
</div>
Основные файлы в cucumber - это текстовый .feature файл с описанием сценариев и .java файлы с описанием реализации шагов выполнения сценариев. Никаких обязательств, кроме расширения, на имена этих файлов не накладывается. Но отсюда следует еще один момент - шаг, описанный в одном текстовом файле будет искаться во всех java-реализациях. Т.е. надо понимать, что две реализации одинаково описанных шагов недопустимы!<br />
<h3>
Интеграция с популярными библиотеками тестирования</h3>
<div style="text-align: left;">
Cucumber прекрасно интегрируется в существующие библиотеки для запуска тестов, такие как <a href="http://www.dokwork.ru/2012/04/junit-4.html" target="_blank">JUnit</a> и TestNG.</div>
<h4 style="text-align: left;">
Интеграция с JUnit</h4>
<div>
Для запуска сценариев с помощью JUnit, в проект необходимо добавить зависимость от, собственно, Cucumber-а и библиотеки для его интеграции с JUnit:</div>
<div class="code">
<pre><code class="xml"><!-- Реализация Cucumber для Java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.8</version>
</dependency>
<!-- Библиотека для интеграции Cucumber с JUnit -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.1.8</version>
</dependency></code></pre>
</div>
Для Cucumber реализован свой org.junit.runner.Runner:
<br />
<div class="code">
<pre><code class="java">import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
public class CucumberTestRunner {
}</code></pre>
</div>
<h4 style="text-align: left;">
Интеграция с TestNG</h4>
В случае с TestNG, CucumberTestRunner должен наследоваться от AbstractTestNGCucumberTests:
<br />
<div class="code">
<pre><code class="java">import cucumber.api.testng.AbstractTestNGCucumberTests;
public class CucumberTests extends AbstractTestNGCucumberTests {
}</code></pre>
</div>
Как и в случае с JUnit, вы должны добавить в зависимости проекта библиотеку интеграции Cucumber с TestNG:
<br />
<div class="code">
<pre><code class="xml"><!-- Реализация Cucumber для Java -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.1.8</version>
</dependency>
<!-- Библиотека для интеграции Cucumber с JUnit -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-testng</artifactId>
<version>1.1.8</version>
</dependency></code></pre>
</div>
<h3 style="text-align: left;">
Способы описать параметры</h3>
В самом начале статьи я привел пример описания параметризованного сценария. Возможность описывать аргументы непосредственно в текстовом файле - очень мощная фича cucumber!<br />
<br />
Этот фреймворк предоставляет несколько механизмов описания параметров тестов прямо в текстовом файле.<br />
<br />
Первый - это группы в регулярном выражении, связывающем описание шага с его реализацией:
<br />
<div class="text">
<pre>Feature: Example of taking the test arguments
Scenario: Take argument from string
Given some number value '12.3'
Given some string value 'Hello world!'
Given some date value 01.08.2015</pre>
</div>
<div class="code">
<pre><code class="java">public class ExampleSteps {
@Given("^some number value '(.*)'")
public void givenNumber(double number) {
System.out.println(number);
}
@Given("^some string value '(.*)'")
public void givenString(String str) {
System.out.println(str);
}
@Given("^some date value (.*)")
public void givenDate(@Format("dd.MM.yyyy") Date date) {
System.out.println(date);
}
}</code></pre>
</div>
<div class="console">
<pre>12.3
Hello world!
Sat Aug 01 00:00:00 MSK 2015</pre>
</div>
Не плохо! Но что, если надо выполнить один и тот же сценарий с разными аргументами? На этот случай в Cucumber есть возможно перечислить все возможные значения аргумента в ASCII таблице:
<br />
<div class="text">
<pre>Given some list of values
|a|
|b|
|c|</pre>
</div>
<div class="code">
<pre><code class="java">@Given("^some list of values")
public void givenList(List<String> list) {
for (String s : list) {
System.out.println(s);
}
}</code></pre>
</div>
<div class="console">
<pre>a
b
c</pre>
</div>
Или описать ассоциативный массив:
<br />
<div class="text">
<pre>Given some map of values
|a|1|
|b|2|
|c|3|</pre>
</div>
<div class="code">
<pre><code class="java">@Given("^some map of values")
public void givenMap(Map<String, Integer><string> map) {
for (Map.Entry<string integer=""> entry : map.entrySet()) {
System.out.println(entry);
}
}</string></string></code></pre>
</div>
<div class="console">
<pre>a=1
b=2
c=3</pre>
</div>
Для сценария с несколькими аргументами и вариантами, можно использовать ASCII таблицу с именованными столбцами:
<br />
<div class="text">
<pre> Scenario Outline: Take few arguments from table
Given argument <a>, argument <b>
Examples:
| a | b |
| 1 | one |
| 2 | two |</pre>
</div>
<div class="code">
<pre><code class="java">@Given("^argument (.*), argument (.*)")
public void givenFewArguments(int a, String b) {
System.out.println(a + "\t" + b);
}</code></pre>
</div>
<div class="console">
<pre>1 one
2 two</pre>
</div>
Обратите внимание на описание сценария, при реализации данного подхода. Вместо ключевого слова Scenario здесь используется Scenario Outline.<br />
<br />
На этом функциональность библиотеки не заканчивается и Cucumber предлагает описывать в качестве аргументов целые классы!<br />
<div class="text">
<pre>Given some users
| name | age |
| Jon | 18 |
| Anna | 23 |</pre>
</div>
<div class="code">
<pre><code class="java">public class User {
String name;
int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}</code></pre>
</div>
<div class="code">
<pre><code class="java">@Given("^some users$")
public void givenSomeUsers(List<User> users) throws Throwable {
for (User user : users) {
System.out.println(user);
}
}</code></pre>
</div>
<div class="console">
<pre>User{name='Jon', age=18}
User{name='Anna', age=23}</pre>
</div>
Здесь стоит обратить внимание на то, что видимость полей в классе User не имеет значения, а для его инстанцирования используется хитрая ReflectionFactory, что приводит к тому, что конструктор вызван не будет!.<br />
<h3 style="text-align: left;">
Предустановки</h3>
<div>
<div dir="ltr" trbidi="on">
В начале каждого сценария cucumber запускает шаги описанные в блоке background:<br />
<div class="text">
<pre>Feature: Example
Background: prepare for scenario
</pre>
<pre> Given any action before every scenario</pre>
</div>
Также есть возможность определить методы, выполняющиеся до и после каждого шага. Для этого существуют аннотации @Before и @After:<br />
<div class="code">
<pre><code class="java">public class ExampleSteps {
@Before
public void setUp() {
}
@After
public void tearDown() {
}
}</code></pre>
</div>
Аннотаций, аналогичных @BeforeClass и @AfterClass в JUnit, в cucumber нет. Но если они очень понадобятся, можно использовать обходной путь:<br />
<div class="text">
<pre>Feature: Example
Background: do it at onece
Given do something at once
</pre>
</div>
<div class="code">
<pre><code class="java">public class ExampleSteps {
boolean isFirstExecution = true;
@Given("^do something at once")
public void initialization(String str) {
if (isFirstExecution) {
// do something...
isFirstExecution = false;
}
}
}</code></pre>
<div>
<code class="java"><br /></code></div>
</div>
</div>
</div>
<h3 style="text-align: left;">
Не просто Cucumber - Огурец!</h3>
Еще одной эффектной особенностью Cucumber, является поддержка множества языков при описании сценариев. Среди них есть и Русский!
<br />
<div class="text">
<pre># language: ru
Функционал: Калькулятор
Структура сценария: Суммирование двух чисел
Допустим дано два числа <a> и <b>
Если сложить их
То получим <результат>
Примеры:
| a | b | результат |
| 3 | 2 | 5 |
</pre>
</div>
Хорошо это или плохо, но аннотации тоже переведены на Русский:<br />
<div class="code">
<pre><code class="java">@Дано("^дано два числа (-?\\d+.?\\d*) и (-?\\d+.?\\d*)")
public void given(double a, double b) {
...
}
@Если("^сложить их")
public void when_sum() {
...
}
@Тогда("^получим (-?\\d+.?\\d*)")
public void then(double res) {
...
}</code></pre>
</div>
Впрочем, здесь фреймворк оставляет за вами выбор: использовать ли локализованные имена аннотаций, или пользоваться английскими. Вот перечень ключевых слов и их эквиваленты для русской локали:<br />
<div class="text">
<pre> "name": "Russian", "native": "русский",
"feature": "Функция|Функционал|Свойство",
"background": "Предыстория|Контекст",
"scenario": "Сценарий",
"scenario_outline": "Структура сценария",
"examples": "Примеры",
"given": "*|Допустим|Дано|Пусть",
"when": "*|Если|Когда",
"then": "*|То|Тогда",
"and": "*|И|К тому же|Также",
"but": "*|Но|А"</pre>
</div>
Полный перечень соответствия для всех поддерживаемых языков можно найти в файле <span style="font-family: Courier New, Courier, monospace;">i18n.json</span>, который находится в <span style="font-family: Courier New, Courier, monospace;">gherkin-2.12.2.jar </span>в пакете <span style="font-family: Courier New, Courier, monospace;">gherkin</span>.
<br />
<h3 style="text-align: left;">
Не всем огурцы по нраву...</h3>
<iframe allowfullscreen="" frameborder="0" height="355" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/key/dRFTLJRYPcDPuK" style="border-width: 1px; border: 1px solid #CCC; margin-bottom: 5px; max-width: 100%;" width="425"> </iframe> <br />
<div style="margin-bottom: 5px;">
<strong> <a href="https://www.slideshare.net/orgeirIngvarsson/pptx8-38364112" target="_blank" title="The Dangers of Cucumber">The Dangers of Cucumber</a> </strong> from <strong><a href="https://www.slideshare.net/orgeirIngvarsson" target="_blank">Þorgeir Ingvarsson</a></strong> </div>
<br />
Суть презентации, на мой взгляд:<br />
<blockquote class="tr_bq">
если вы стремитесь к тому, чтобы вашим фреймворком могли пользоваться даже дебилы, именно они им и будут пользоваться.</blockquote>
<h4 style="text-align: left;">
...но</h4>
Если подходить к пользованию Cucumber с умом и к месту, можно поиметь не мало счастья.<br />
<br />
Предложенный автором статьи "Введение в BDD" подход к написанию тестов выглядит действительно многообещающим. Но на практике имена из серии shouldDoSomething очень трудно воспринимаются при чтении кода и требуют достаточно много усилий для того, чтобы понять, какой именно смысл вложен в их название. Немногим лучше обстоят дела в Scala с фреймворком <a href="https://etorreborre.github.io/specs2/" target="_blank">specs2</a>. Но код тестов все еще остается трудно читаемый.<br />
<span class="pointer" onclick="$('#div_spec2').toggle();">Пример теста на spec2</span>
<br />
<div class="code" id="div_spec2" style="display: none;">
<pre><code class="java">class HelloWorldSpec extends Specification {
"This is a specification for the 'Hello world' string".txt
"The 'Hello world' string should" >> {
"contain 11 characters" >> {
"Hello world" must haveSize(11)
}
"start with 'Hello'" >> {
"Hello world" must startWith("Hello")
}
"end with 'world'" >> {
"Hello world" must endWith("world")
}
}
}</code></pre>
</div>
<br />
Почему на мой взгляд важно, чтобы код тестов был прост для понимания? При анализе отчетов о прогоне тестов, найдя упавший тест, первый вопрос на который вам надо найти ответ: "а <b>что</b>, собственно, тест делает?", и только потом разбираться с тем, <b>как</b> он это делает. В моем представлении Cucumber - отличное решение, позволяющее минимизировать усилия и время для ответа на первый вопрос, и сосредоточиться на решении более важного, второго.<br />
<br />
Замечу, что если речь идет о модульных тестах, которые по своему определению просты и компактны, то использование Cucumber скорее всего будет лишним. Но в случае больших и подчас запутанных функциональных тестов, очень важно, чтобы их сценарии были описаны в простом и строго структурированном виде. И именно здесь Cucumber может проявить себя с лучшей стороны!<br />
<br /></div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com2tag:blogger.com,1999:blog-7407396207758383724.post-68478899945409055942015-07-24T10:24:00.000+03:002015-07-24T10:26:25.292+03:00Sublime Text - простое решение непростых задач<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqi_9TBIlX_SnMfXkFFhtbwLuk6BAt8NMHMjXf6Q5BikQsRdCrGlmdRLGvnM2Z3yiD4ijDmM3AwP7GsLDqv-0NxjOTRLm5E_TpDzdnMxUQ2A20ZVfc3X4Y7ZEy_wTg9LbzppylwwJ0qlL5/s1600/sublime_text_icon.png" style="padding: 10px;" width="120px" />
</td><td>Intellij Idea стала на столько привычным окном в мир большой и запутанной Java, что мимо незаметно пролетела такая прелесть как Sublime Text. Небольшое увлечение решением задач на <a href="http://www.codewars.com/r/6WUgkA" target="_blank">codewars</a> заставило отложить Idea в сторону и поискать решение "полегче" и "пошустрее". Очень хотелось держать перед глазами одно единственное окно с редактором и терминалом. Все мои попытки примириться с vim и screen так и не увенчались успехом и у меня появился повод присмотреться к Sublime.<br />
Если вы из <a class="tooltip" href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D0%BE%D1%83%D0%BF%D0%BE%D0%BA" target="_blank">моей команды<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiuPxo40Kpt5fUbkPXNxkU1PmvwQZv9TmeE8d5-A_fb6AEwHN2DwWtPZfWN-2rbyyb-jHGPxKNTMXAfJ1miuewQHl0qIRewJ6DT1Vp2TDNvaT9ZQsfZqbyksS8YUQI9TlVBLD2EJzh17p4R/s1600/slowpoke.png" width="80px" /></a>, прошу под кат за моим списком полезных горячих клавиш, расширений и прочих фишечек sublime</td></tr>
</tbody></table>
<a name='more'></a><h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv4vzzZ42a2IxJy1oSHFjnKhNPAHhJFRiOWMeSbnWhKohyphenhyphen59OrDfUUt3HGJfW9OzFKrKn8PwNP3qPqywIQwAAgCHmnrapw3dp_5Fv2SaHYK5O4TH2PYhRmF5tqIXvOO2m1F5t5eC3w2DGF/s1600/board.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiv4vzzZ42a2IxJy1oSHFjnKhNPAHhJFRiOWMeSbnWhKohyphenhyphen59OrDfUUt3HGJfW9OzFKrKn8PwNP3qPqywIQwAAgCHmnrapw3dp_5Fv2SaHYK5O4TH2PYhRmF5tqIXvOO2m1F5t5eC3w2DGF/s200/board.png" width="156" /></a></h2>
<h3 style="text-align: left;">
Простое управление расширениями</h3>
<div>
Вся мощь Sublime Text кроется в ее многочисленных расширениях. Более простого механизма их управления мне еще не встречалось! </div>
<div>
<div style="text-align: left;">
<br /></div>
<a href="https://packagecontrol.io/" target="_blank">Package control</a> - менеджер расширений, делающий процесс управления плагинами для Sublime чрезвычайно простым. Достаточно набрать ключевое слово из интересующего вас контекста, и перед вами предстанет список всех подходящий расширений:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF6buNBrI0uEto5y1KfJj-ycmtCyx8Y9cQ8Y8i0X_Ca9xuEHoyEAMn2m49c9gT8mHkfivtkKgMIqjZUPTrd80FC0k4v50QLdm6-UeJ6Tg76zIx_Gr_MdURrl_i2Ah8MENOvfWS_llgMEJl/s1600/sublime-text-2_package-control.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="237" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF6buNBrI0uEto5y1KfJj-ycmtCyx8Y9cQ8Y8i0X_Ca9xuEHoyEAMn2m49c9gT8mHkfivtkKgMIqjZUPTrd80FC0k4v50QLdm6-UeJ6Tg76zIx_Gr_MdURrl_i2Ah8MENOvfWS_llgMEJl/s320/sublime-text-2_package-control.png" width="320" /></a></div>
<br />
<h3 style="text-align: left;">
Гибкая конфигурация</h3>
</div>
<div>
Возможности кастомизации поведения и внешнего вида Sublime Text практически безграничны. Все настройки описываются в текстовых файлах и делятся на настройки по умолчанию и пользовательские настройки. Принцип настраивания прост:</div>
<div>
<ol style="text-align: left;">
<li>Открываете файл с дефолтными настройками интересующего вас аспекта(общие настройки редактора, привязки клавишь, настройки расширений и т.д.): Preferences -> [Settings-Default, Key Bindings-Default, и т.д.]</li>
<li>Находите интересующий вас параметр</li>
<li>Копируете его в аналогичный файл с пользовательскими настройками: Preferences -> [Settings-User, Key Bindings-User, и т.д.]</li>
<li>Изменяете его значение</li>
<li>Сохраняете файл</li>
</ol>
В качестве примера, настроим sublime вставлять пробелы вместо табуляции. Скорее всего такая настройка если и есть, то находится где-то в файле Settings. Откроем его версию по умолчанию (Preferences -> Settings-Default) и поищем по вхождению слово 'space':</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_Fcb0zNMtA2Jaty5-K0_wKcOHJEa85totdFaM-I4qlQayEvy9yWHNRzsF-SGH3dU-dmCYU40h01lvRFaR3FTLBm_7MVOZFhom2H_bMsGRY7_56SJOS3t8iEtQvfFnc94mOuLFeOhIFSvy/s1600/space.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="84" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_Fcb0zNMtA2Jaty5-K0_wKcOHJEa85totdFaM-I4qlQayEvy9yWHNRzsF-SGH3dU-dmCYU40h01lvRFaR3FTLBm_7MVOZFhom2H_bMsGRY7_56SJOS3t8iEtQvfFnc94mOuLFeOhIFSvy/s320/space.png" width="320" /></a></div>
<div>
Название параметра говорит само за себя и на всякий случай сопровождается исчерпывающим комментарием. Копируем настройку "translate_tabs_to_spaces" в пользовательский файл настроек (Preferences -> Settings-User) со значением true, сохраняем файл.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis6hyfYE8SYN6l76s9lt3Jj2OE7HGIgxAMkEtyetW5ulu-vOD3iNWCkK7ZnGRoujmnz-Gl4vMznNctcv072tN0UV8mOUlXLbEN8eD6VwJqlX4Qd1GwegLPf4yyVuJMFlDKxNvifswYUeRi/s1600/tabs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="40" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEis6hyfYE8SYN6l76s9lt3Jj2OE7HGIgxAMkEtyetW5ulu-vOD3iNWCkK7ZnGRoujmnz-Gl4vMznNctcv072tN0UV8mOUlXLbEN8eD6VwJqlX4Qd1GwegLPf4yyVuJMFlDKxNvifswYUeRi/s320/tabs.png" width="320" /></a></div>
<div>
Теперь по нажатию кнопки <TAB> будут вставляться пробелы вместо символа '\t'. Количество пробелов задается параметром "tab_size" и тоже может быть изменено.</div>
<div>
<br /></div>
<div>
<ol style="text-align: left;">
</ol>
</div>
<div>
<h3>
Подсветка синтаксиса</h3>
</div>
<div>
Sublime умеет подсвечивать содержимое файлов определяя синтаксис по расширению. Из коробки предлагается большое количество готовых тем, но если их окажется недостаточно, всегда можно установить дополнительные. В частности, для любителей темной темы из Idea ее портировали в sublime. Достаточно поставить соответствующее расширение <a href="https://packagecontrol.io/packages/Darkula%20Color%20Scheme" target="_blank">Darkula Color Scheme</a>.</div>
<div>
Тема по умолчанию настраивается в конфигурационном файле <span style="font-family: Courier New, Courier, monospace;">Preferences.sublime-settings</span>. Вы можете найти его в своей домашней директории <span style="font-family: Courier New, Courier, monospace;">.config/sublime-text-3/Packages/User/</span> или открыть через меню: <span style="font-family: Courier New, Courier, monospace;">Preferenses -> Settings-User</span><span style="font-family: inherit;">:</span></div>
<div class="text">
"color_scheme": "Packages/Darkula Color Scheme/darkula.tmTheme"</div>
<h3 style="text-align: left;">
Автоматическое форматирование</h3>
<div>
Автоматическое форматирование кода - одна из тех фишек, что я люблю в IDE. Из коробки Sublime умеет разве что немного табуляцию поправить, но существует огромное количество расширений, позволяющих выполнять авто-форматирование на гораздо более качественном уровне. </div>
<div>
<br /></div>
<div>
Если вам приходится часто работать в разных средах и хочется везде иметь одинаковые настройки форматирования, вам поможет в этом <a href="http://editorconfig.org/" target="_blank">EditorConfig</a>. Это плагин для большинства популярных сред разработки, унифицирующий описание форматирования для исходных кодов.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMclpH8fEB623U-1FmKqL_vlmAD9J7eGYXlw9Mc8mims97KsvS6OMeks8jLsxt1RGRrhkRlqTTkKrvTfswUEWW3ahBl7HGQiiwmYHgo-htN1yvUkEX-xCgrFRB4vM9fQOAzxSEbrK4fGVu/s1600/72615c0be66f4fe189e0063d7062543d.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="146" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMclpH8fEB623U-1FmKqL_vlmAD9J7eGYXlw9Mc8mims97KsvS6OMeks8jLsxt1RGRrhkRlqTTkKrvTfswUEWW3ahBl7HGQiiwmYHgo-htN1yvUkEX-xCgrFRB4vM9fQOAzxSEbrK4fGVu/s320/72615c0be66f4fe189e0063d7062543d.png" width="320" /></a></div>
</div>
<h3 style="text-align: left;">
Autocomplete</h3>
<div>
При наборе текста Sublime анализирует его и предоставляет наиболее релевантные варианты его завершения. Из коробки работает в основном с содержимым открытого файла, но может быть улучшен с помощью дополнений, например <a href="https://github.com/SublimeCodeIntel/SublimeCodeIntel" target="_blank">SublimeCodeIntel</a>. <i>Внимание! Индексирование может занять некоторое время и Sublima может перестать быть привычно шустрой.</i><br />
<h3 style="text-align: left;">
Snippets</h3>
</div>
<div>
Snippet - это заранее подготовленный шаблон кода, который может быть быстро вставлен в sublime:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9_MxYDsgpKs65BL4zcYb0sMgVblyMTuzAVu4w7-5IdmmGzae-gg1NNZCIeU6rHVLmU6Itp6kpXSCXFpaB3JlJ_PIYrhEUEQQKSoNPZIbTQEvv4TQ9Zz2Wpnw1jvWWpulQBDveYncOACla/s1600/snippet.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9_MxYDsgpKs65BL4zcYb0sMgVblyMTuzAVu4w7-5IdmmGzae-gg1NNZCIeU6rHVLmU6Itp6kpXSCXFpaB3JlJ_PIYrhEUEQQKSoNPZIbTQEvv4TQ9Zz2Wpnw1jvWWpulQBDveYncOACla/s320/snippet.gif" width="320" /></a></div>
</div>
Каждый сниппет описывается в отдельном файле <span style="font-family: Courier New, Courier, monospace;"> ~/.config/sublime-text-3/Packages/User{Snippet Name}.sublime-snippet</span>.<br />
Шаблон файла можно быстро создать через меню: <span style="font-family: Courier New, Courier, monospace;">Tools -> New Snippet</span>. Синтаксис описания сниппет очень прост:
<br />
<div class="code">
<pre><code class="xml"><!-- Блоки кода вида ${number:string} - это участки автоматической подставновки,
которые будут последовательно выделяться(для замены на необходимое содержание) при нажатии клавиши Tab-->
<snippet>
<content><![CDATA[
function ${1:function_name} (${2:argument}) {
${3:// body...}
}
]]></content>
<!-- Необязательный параметр: указывает сокращение по которому будет вставляться сниппет -->
<tabTrigger>fun</tabTrigger>
<!-- Необязательный параметр: указывает тип файла для которого предназначен сниппет -->
<scope>source.js</scope>
</snippet></code></pre>
</div>
<span class="pointer" onclick="$('#scope_div').toggle();">
Возможные варианты для scope</span>
<br />
<div class="text" id="scope_div" style="display: none;">
<pre>ActionScript: source.actionscript.2
AppleScript: source.applescript
ASP: source.asp
Batch FIle: source.dosbatch
C#: source.cs
C++: source.c++
Clojure: source.clojure
CoffeeScript: source.coffee
CSS: source.css
D: source.d
Diff: source.diff
Erlang: source.erlang
Go: source.go
GraphViz: source.dot
Groovy: source.groovy
Haskell: source.haskell
HTML: text.html(.basic)
JSP: text.html.jsp
Java: source.java
Java Properties: source.java-props
Java Doc: text.html.javadoc
JSON: source.json
Javascript: source.js
BibTex: source.bibtex
Latex Log: text.log.latex
Latex Memoir: text.tex.latex.memoir
Latex: text.tex.latex
LESS: source.css.less
TeX: text.tex
Lisp: source.lisp
Lua: source.lua
MakeFile: source.makefile
Markdown: text.html.markdown
Multi Markdown: text.html.markdown.multimarkdown
Matlab: source.matlab
Objective-C: source.objc
Objective-C++: source.objc++
OCaml campl4: source.camlp4.ocaml
OCaml: source.ocaml
OCamllex: source.ocamllex
Perl: source.perl
PHP: source.php
Regular Expression(python): source.regexp.python
Python: source.python
R Console: source.r-console
R: source.r
Ruby on Rails: source.ruby.rails
Ruby HAML: text.haml
SQL(Ruby): source.sql.ruby
Regular Expression: source.regexp
RestructuredText: text.restructuredtext
Ruby: source.ruby
SASS: source.sass
Scala: source.scala
Shell Script: source.shell
SQL: source.sql
Stylus: source.stylus
TCL: source.tcl
HTML(TCL): text.html.tcl
Plain text: text.plain
Textile: text.html.textile
XML: text.xml
XSL: text.xml.xsl
YAML: source.yaml</pre>
</div>
<div style="text-align: left;">
Более подробно про сниппеты можно почитать <a href="http://habrahabr.ru/post/148324/" target="_blank">на хабре</a>.</div>
<h3 style="text-align: left;">
Встроенный тайлинг</h3>
<div>
Для любителей тайлинга (возможности автоматического размещения окон на экране с помощью горячих клавиш) в sublime он есть из коробки:</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;">
<tbody>
<tr>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipGKqCKVBcn5EWX14qrbdxBDPs_9ySnhPTt5_a_V3knWZ1PCF4_-ctvWhrz-o454Y0pS_BEAdJYF7mGm-Zhp784kDJHVhbouBjLss8Bllkb2oFbnTkVDLBx5iQftTsHl3Hy6ESr09YNxZq/s1600/alt_shift_2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipGKqCKVBcn5EWX14qrbdxBDPs_9ySnhPTt5_a_V3knWZ1PCF4_-ctvWhrz-o454Y0pS_BEAdJYF7mGm-Zhp784kDJHVhbouBjLss8Bllkb2oFbnTkVDLBx5iQftTsHl3Hy6ESr09YNxZq/s200/alt_shift_2.png" width="200" /></a>
</td>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrrrkgwS7sSRkRLzRCiva2WUT1IFnToWbfPvq6X_vFpdgIc8r5KfGGTm4qIbp0CXYPmMzcJHz1LUbwliyu59JyPW3IYew28htfnTuvlBn9kXjavjoHJ8sF2EDsqQphFADF2u6hRurYUAlH/s1600/alt_shift_3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrrrkgwS7sSRkRLzRCiva2WUT1IFnToWbfPvq6X_vFpdgIc8r5KfGGTm4qIbp0CXYPmMzcJHz1LUbwliyu59JyPW3IYew28htfnTuvlBn9kXjavjoHJ8sF2EDsqQphFADF2u6hRurYUAlH/s200/alt_shift_3.png" width="200" /></a>
</td>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidyhVzILazegnjNvT9E5JLQnIKggAA1MtHYsoL3wNqwU8feywcxObrJz2-L-Bg5xBKlQZ8uBSrU1jVTsfyaJi9srhklAmZbfJwWPKYQ5QAxjmLYpisJlnmtxDReBwhf3ilr8RfXdCqwZ_y/s1600/alt_shift_4.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidyhVzILazegnjNvT9E5JLQnIKggAA1MtHYsoL3wNqwU8feywcxObrJz2-L-Bg5xBKlQZ8uBSrU1jVTsfyaJi9srhklAmZbfJwWPKYQ5QAxjmLYpisJlnmtxDReBwhf3ilr8RfXdCqwZ_y/s200/alt_shift_4.png" width="200" /></a>
</td>
</tr>
<tr>
<td class="tr-caption" style="text-align: center;">alt+shift+2</td>
<td class="tr-caption" style="text-align: center;">alt+shift+3</td>
<td class="tr-caption" style="text-align: center;">alt+shift+4</td>
</tr>
<tr>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrHJTWfFXb5J-PEkJAAuuPk4v7f1FH5xHMXS7nTTDguw1kFSbiwCceXJ3g-Xy4BW-9Nl-LIHkfa-2GemrLcH9fI-mBnyLofkcCOXSMq26hLM4SdXCU7yD_OcOLTLcGA2g8qDaUEUi3bSD9/s1600/alt_shift_5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrHJTWfFXb5J-PEkJAAuuPk4v7f1FH5xHMXS7nTTDguw1kFSbiwCceXJ3g-Xy4BW-9Nl-LIHkfa-2GemrLcH9fI-mBnyLofkcCOXSMq26hLM4SdXCU7yD_OcOLTLcGA2g8qDaUEUi3bSD9/s200/alt_shift_5.png" width="200" /></a>
</td>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisT6MH29B0NU6azFnYOiEDEF5UjUEmQbDFmJYe9N2gpI_c1QfDBs0X_4TKdDzgBbIMo2Wcbu3AB67OyozNJgp9w1uX7nyAxx7hLQI3-AmsBTH4TX6_kknuhzS-Fx6IqBv6XnEXkijZiHKj/s1600/alt_shift_8.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisT6MH29B0NU6azFnYOiEDEF5UjUEmQbDFmJYe9N2gpI_c1QfDBs0X_4TKdDzgBbIMo2Wcbu3AB67OyozNJgp9w1uX7nyAxx7hLQI3-AmsBTH4TX6_kknuhzS-Fx6IqBv6XnEXkijZiHKj/s200/alt_shift_8.png" width="200" /></a>
</td>
<td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBCB4MYG1ghIbwnFh0S4mkYyS11i81-X4knQzc3uj_a9InfTVITQNN9Kj4HmJ6SQf_wLCIUwh789rePy6q_GGC03wCO_Ubk9oSZC8z1LbnYeASCBmzkaFb4FkoW9PIdOIhN3gpupWNkzhj/s1600/alt_shift_9.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBCB4MYG1ghIbwnFh0S4mkYyS11i81-X4knQzc3uj_a9InfTVITQNN9Kj4HmJ6SQf_wLCIUwh789rePy6q_GGC03wCO_Ubk9oSZC8z1LbnYeASCBmzkaFb4FkoW9PIdOIhN3gpupWNkzhj/s200/alt_shift_9.png" width="200" /></a>
</td>
</tr>
<tr>
<td class="tr-caption" style="text-align: center;">alt+shift+5</td>
<td class="tr-caption" style="text-align: center;">alt+shift+8</td>
<td class="tr-caption" style="text-align: center;">alt+shift+9</td></tr>
</tbody></table>
<div style="text-align: left;">
<i>Для размещения открытых файлов в разных частях экрана используются группы: </i></div>
<div style="text-align: left;">
<i><span style="font-family: Courier New, Courier, monospace;">view -> groups</span></i></div>
<h3 style="text-align: left;">
Полезные расширения</h3>
<h4>
<a href="https://packagecontrol.io/packages/Git" target="_blank">Git</a></h4>
<div>
Встраивает команды git-а прямо в sublime. Просто откройте командную палитру (Ctrl+Shist+P) и наберите команду git:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://packagecontrol.io/readmes/img/e95969b07ac68c91eacba39d8c30543e727f06d0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://packagecontrol.io/readmes/img/e95969b07ac68c91eacba39d8c30543e727f06d0.png" width="320" /></a></div>
<h4 style="text-align: left;">
<a href="https://packagecontrol.io/packages/GitGutter" target="_blank">GitGutter</a></h4>
<div>
Встроенная подсветка изменений фалов:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://packagecontrol.io/readmes/img/cb42e7cddad0c04794b783742ee8f2085e95295a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://packagecontrol.io/readmes/img/cb42e7cddad0c04794b783742ee8f2085e95295a.png" /></a></div>
<div>
<br /></div>
<h4 style="text-align: left;">
<a href="https://packagecontrol.io/packages/HTML-CSS-JS%20Prettify" target="_blank">HTML-CSS-JS Prettify</a></h4>
<div>
Автоматическое форматирование HTML, CSS и JavaScript файлов:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAAGJSFJypAT_HKMi0QwL5VRkZoVbj5e_pA4Y7G85L1J45k5a5ju76XnNWiAdB0oykk9Ww6e06BYnRUEsZSQQAUE43UdSytROif_glsm7Lc8tdYf7-lxlB5gpsoeoQhU0VL4W4tvKML5cR/s1600/formatting.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAAGJSFJypAT_HKMi0QwL5VRkZoVbj5e_pA4Y7G85L1J45k5a5ju76XnNWiAdB0oykk9Ww6e06BYnRUEsZSQQAUE43UdSytROif_glsm7Lc8tdYf7-lxlB5gpsoeoQhU0VL4W4tvKML5cR/s320/formatting.gif" width="320" /></a></div>
<div>
Единственный важный момент: для форматирования JavaScript нужен Node.js framework.<br />
<h4 style="text-align: left;">
<a href="https://packagecontrol.io/packages/AutoFileName" target="_blank">AutoFileName</a></h4>
</div>
<div>
Закончит за вас указание пути к файлу в коде:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCc1Ajw-P2A9N_DcysZpph7i5w-kvztB0XkqDsdUdBOcw6VojxnPaw91G546PqoOvSHEg00R1NGmCxJ1DdqUPMt6PX-C4aJh_SQR6RyEwATXaPH5Ef1gMvXq-9B7uPTnH6ppWOtAEHXiaL/s1600/autofilename.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="139" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCc1Ajw-P2A9N_DcysZpph7i5w-kvztB0XkqDsdUdBOcw6VojxnPaw91G546PqoOvSHEg00R1NGmCxJ1DdqUPMt6PX-C4aJh_SQR6RyEwATXaPH5Ef1gMvXq-9B7uPTnH6ppWOtAEHXiaL/s320/autofilename.png" width="320" /></a></div>
<h4 style="text-align: left;">
<a href="https://packagecontrol.io/packages/ColorPicker" target="_blank">ColorPicker</a></h4>
<div>
С ColorPicker вам больше не придется открывать Gimp чтобы узнать код цвета!</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHV5g6bGvKtDXvFa8nIH9dOYCgYCGZzs1vhxR9I5sQKUzCatXRbb7kqzUYC4zEyXZvpVb4URw9g4XQJQsUx391HoOni35NWZ-C4lHTmvGRl7vvOJ5uhjTZACmV8mjkitDqhyLmB5YGDlhG/s1600/ColorPicker.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHV5g6bGvKtDXvFa8nIH9dOYCgYCGZzs1vhxR9I5sQKUzCatXRbb7kqzUYC4zEyXZvpVb4URw9g4XQJQsUx391HoOni35NWZ-C4lHTmvGRl7vvOJ5uhjTZACmV8mjkitDqhyLmB5YGDlhG/s320/ColorPicker.png" width="320" /></a></div>
<div>
<br /></div>
<h4 style="text-align: left;">
<a href="https://github.com/Monnoroch/ColorHighlighter" target="_blank">ColorHighlighter</a></h4>
<div>
Чтобы наглядно представлять как выглядит цвет, описаный в css-атрибуте, стоит присмотреться к расширению ColorHighlighter. Это расширение позволяет наглдно видеть цвет, описанный в css:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh19hVy-xksJBb0MIkXKoDF-2TTwg0wBiTeDcw6F5oWLusVZhl4dxrms2MY6grdWfIu09vi-FY9xUYUJqW5NLAByAFcrALiNJFT5OOI_KHSHLOgkRD4x8szbgAsZQ1iPatziHMcxH8GlYTm/s1600/ColorHighlighter.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh19hVy-xksJBb0MIkXKoDF-2TTwg0wBiTeDcw6F5oWLusVZhl4dxrms2MY6grdWfIu09vi-FY9xUYUJqW5NLAByAFcrALiNJFT5OOI_KHSHLOgkRD4x8szbgAsZQ1iPatziHMcxH8GlYTm/s320/ColorHighlighter.gif" width="320" /></a></div>
<div>
<br /></div>
<h4 style="text-align: left;">
<a href="https://github.com/rmaksim/Sublime-Text-2-Goto-CSS-Declaration" target="_blank">Goto-CSS-Declaration</a></h4>
<div>
Goto-CSS-Declaration позволяет перейти к описанию css-стиля в один клик!</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrkzzKNI4WX_IZDDUegTva03Q6EIXeJ8IeniIzol4i3lXclIJ8GXE0k2p6jafSZkQhuqXxc6u3h8TzWmQKkVGNEbpRN6ZJrzP9NTnHoWVLZanCzytAsRh0WFbSpp78yug3gLIQNF7p9y4k/s1600/goto_css_declaration.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="118" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrkzzKNI4WX_IZDDUegTva03Q6EIXeJ8IeniIzol4i3lXclIJ8GXE0k2p6jafSZkQhuqXxc6u3h8TzWmQKkVGNEbpRN6ZJrzP9NTnHoWVLZanCzytAsRh0WFbSpp78yug3gLIQNF7p9y4k/s320/goto_css_declaration.gif" width="320" /></a></div>
<div>
<br /></div>
<h4 style="text-align: left;">
<a href="https://packagecontrol.io/packages/Terminality" target="_blank">Terminality</a></h4>
<div>
Расширение позволяет открыть терминал операционной системы прямо в окне редактора:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbDf6geHF9B2xUNK2sK86CB_do6Q5JdHoXlQbNlywE5TQl3kcYBR25_Vf2PJKa8rkEWYh0hiByXk5f-dJlL_t18kRV0xK0p350Tk5jBkk6bfP8iIQcNZz1SXc1S_DXAGYZaez1qwH5wIqa/s1600/terminality.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="153" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbDf6geHF9B2xUNK2sK86CB_do6Q5JdHoXlQbNlywE5TQl3kcYBR25_Vf2PJKa8rkEWYh0hiByXk5f-dJlL_t18kRV0xK0p350Tk5jBkk6bfP8iIQcNZz1SXc1S_DXAGYZaez1qwH5wIqa/s320/terminality.png" width="320" /></a></div>
<br />
Жутко удобно, но явно немного нагружает процессор.</div>
<div>
<br /></div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com2tag:blogger.com,1999:blog-7407396207758383724.post-4021116377666516232015-03-16T12:37:00.000+03:002015-03-16T12:37:51.681+03:00Git: пренос изменений из ветки в ветку с фильтром<div dir="ltr" style="text-align: left;" trbidi="on">
По автору:
<div class="code">
<code>
git rev-list branch_from..branch_to --reverse <b>--author=special_author_name</b> | git cherry-pick --stdin
</code>
</div>
По измененному файлу:
<div class="code">
<code>
git rev-list branch_from..branch_to --reverse <b>-- README</b> | git cherry-pick --stdin
</code>
</div>
... и так далее.
<p/>
<i>Если при переносе будут возникать конфликты, разруливайте их и продолжайте свое темное дело командой</i>
<code>
git cherry-pick --continue
</code>
</div>Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-57202960113158320312014-11-19T15:16:00.000+03:002014-11-19T15:23:33.117+03:00Шпаргалка по bash<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOqAmetjIkJJ_9mU8zM8u9YOL31Ot4G1tHflVlB69ebcem7ULefBHDrPRUzKcXFQW07En3FU54b286Zfmb5NT8fhs-gLUrNVk02aErflIFSaC53awBVGiUr5t0nAermsaLYJs5QkDIta_9/s1600/keep-calm-and-bin-bash.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOqAmetjIkJJ_9mU8zM8u9YOL31Ot4G1tHflVlB69ebcem7ULefBHDrPRUzKcXFQW07En3FU54b286Zfmb5NT8fhs-gLUrNVk02aErflIFSaC53awBVGiUr5t0nAermsaLYJs5QkDIta_9/s1600/keep-calm-and-bin-bash.png" height="180" /></a></div>
<br /></td><td>Перечень базовых конструкций bash.<br />
<br /><div style="text-align: right;">
<i>Отдельное спасибо <a href="http://habrahabr.ru/users/ite/">#ite</a> за статьи:</i></div>
<div style="text-align: right;">
<a href="http://habrahabr.ru/post/47163/">Основы BASH. Часть 1.</a></div>
<div style="text-align: right;">
<a href="http://habrahabr.ru/post/52871/">Основы BASH. Часть 2.</a></div>
<div>
<br /></div>
<a href="https://app.box.com/s/spc5ms8tbt2gduh3ek2u" target="_blank">Шпаргалка BASH.pdf</a></td></tr>
</tbody></table>
<a name='more'></a><h3 style="text-align: left;">
Основные команды</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>break</b> выход из цикла for, while или until</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>continue</b> выполнение следующей итерации цикла for, while или until</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>echo</b> вывод аргументов, разделенных пробелами, на стандартное устройство вывода</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>exit</b> выход из оболочки</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>export</b> отмечает аргументы как переменные для передачи в дочерние процессы в среде</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>hash</b> запоминает полные имена путей команд, указанных в качестве аргументов, чтобы не искать их при следующем обращении</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>kill</b> посылает сигнал завершения процессу</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>let</b> производит арифметические операции над числами и переменными</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>pwd</b> выводит текущий рабочий каталог</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>read</b> читает строку из ввода оболочки и использует ее для присвоения значений указанным переменным</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>return</b> заставляет функцию оболочки выйти с указанным значением</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>shift</b> перемещает позиционные параметры налево</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>test</b> вычисляет условное выражение</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>times</b> выводит имя пользователя и системное время, использованное оболочкой и ее потомками</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>trap</b> указывает команды, которые должны выполняться при получении оболочкой сигнала</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>unset</b> вызывает уничтожение переменных оболочки</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>wait</b> ждет выхода из дочернего процесса и сообщает выходное состояние.</span></li>
</ul>
<h3 style="text-align: left;">
Зарезервированные переменные</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>$DIRSTACK</b> - содержимое вершины стека каталогов</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$EDITOR</b> - текстовый редактор по умолчанию</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$EUID</b> - Эффективный UID. Если вы использовали программу su для выполнения команд от другого пользователя, то эта переменная содержит UID этого пользователя, в то время как...</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$UID</b> - ...содержит реальный идентификатор, который устанавливается только при логине</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$FUNCNAME</b> - имя текущей функции в скрипте</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$GROUPS</b> - массив групп к которым принадлежит текущий пользователь</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$HOME</b> - домашний каталог пользователя</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$HOSTNAME</b> - ваш hostname</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$HOSTTYPE</b> - архитектура машины</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$LC_CTYPE</b> - внутренняя переменная, котороя определяет кодировку символов</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$OLDPWD</b> - прежний рабочий каталог</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$OSTYPE</b> - тип ОС</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$PATH</b> - путь поиска программ</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$PPID</b> - идентификатор родительского процесса</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$SECONDS</b> - время работы скрипта(в сек.)</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$#</b> - общее количество параметров переданных скрипту</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$*</b> - все аргументы переданыне скрипту(выводятся в строку)</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$@</b> - тоже самое, что и предыдущий, но параметры выводятся в столбик</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$!</b> - PID последнего запущенного в фоне процесса</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>$$</b> - PID самого скрипта</span></li>
</ul>
<h3 style="text-align: left;">
Команды возвращающие код возврата</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>test</b> - используется для логического сравнения. после выражения, неоьбходима закрывающая скобка "]"</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>[</b> - синоним команды test</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>[[</b> - расширенная версия "[" (начиная с версии 2.02)(как в примере), внутри которой могут быть использованы || (или), & (и). Должна иметь закрывающую скобку "]]"</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>(( ))</b> - математическое сравнение.</span></li>
</ul>
<h3 style="text-align: left;">
Логические операции</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>-z</b> # строка пуста</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-n</b> # строка не пуста</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>=</b>, (<b>==</b>) # строки равны</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>!=</b> # строки неравны</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-eq</b> # равно</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-ne</b> # неравно</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-lt</b>,(<b><</b>) # меньше</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-le</b>,(<b><=</b>) # меньше или равно</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-gt</b>,(<b>></b>) #больше</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-ge</b>,(<b>>=</b>) #больше или равно</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>!</b> #отрицание логического выражения</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-a</b>,(<b>&&</b>) #логическое «И»</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>-o</b>,(<b>||</b>) # логическое «ИЛИ»</span></li>
</ul>
<h3 style="text-align: left;">
Математические операции</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>+</b> — сложение</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>—</b> — вычитание</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>*</b> — умножение</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>/</b> — деление</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>**</b> — возведение в степень</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>%</b> — модуль(деление по модулю), остаток от деления</span></li>
</ul>
<h3 style="text-align: left;">
Условные операторы</h3>
<ul style="text-align: left;">
<li><span style="font-family: Courier New, Courier, monospace;"><b>if</b> выражение или команда возвращающая код возврата</span></li>
<li><span style="font-family: 'Courier New', Courier, monospace;"><b>then</b> ...</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>else</b> ...</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>fi</b></span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>if</b> выражение или команда возвращающая код возврата</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>then</b> команды</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>elif</b> выражение или команда возвращающая код возврата</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>then</b> команды</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>fi</b></span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>case</b> $переменная <b>in</b></span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><<b>n</b>><b>)</b>команды</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>;;</b></span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>*)</b>команды # выполняются если не было найдено подходящего варианта</span></li>
<li><span style="font-family: Courier New, Courier, monospace;"><b>;;</b></span></li>
</ul>
<h3 style="text-align: left;">
<span style="font-family: 'Courier New', Courier, monospace;">Циклы</span></h3>
<ul style="text-align: left;">
<li>
<span style="font-family: Courier New, Courier, monospace;"><b>for</b> переменная <b>in</b> список_значений</span>
<span style="font-family: Courier New, Courier, monospace;"><b>do</b></span>
<span style="font-family: Courier New, Courier, monospace;">команды</span>
<span style="font-family: Courier New, Courier, monospace;"><b>done</b></span>
</li>
<li>
<span style="font-family: Courier New, Courier, monospace;"><b>while</b> выражение или команда возвращающая код возврата</span>
<span style="font-family: Courier New, Courier, monospace;"><b>do</b></span>
<span style="font-family: Courier New, Courier, monospace;">команды</span>
<span style="font-family: Courier New, Courier, monospace;"><b>done</b></span>
</li>
<li>
<span style="font-family: Courier New, Courier, monospace;"><b>until</b> выражение или команда возвращающая код возврата</span>
<span style="font-family: Courier New, Courier, monospace;"><b>do</b></span>
<span style="font-family: Courier New, Courier, monospace;">команды</span>
<span style="font-family: Courier New, Courier, monospace;"><b>done</b> </span>
</li>
</ul>
<br />
<div>
<!--EndFragment--></div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-25334465491065041922014-09-04T18:24:00.001+04:002015-10-28T10:58:30.260+03:00Установка PostgreSQL сервера в Ubuntu Linux<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2aOoSkxTNhHIO1JzAIT5DSDs00IX9-pIzseoYwhb6h93Ekj2eLh_Ug5T0fWzWmj2Wc4QVRLL8hYCBCg5ucMLibGvpNCCKlGHpDocgE2kMJ7BUHylXVryzDukNvqqyLWk45wGbxFbX5Fcj/s1600/postgresql_ubuntu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2aOoSkxTNhHIO1JzAIT5DSDs00IX9-pIzseoYwhb6h93Ekj2eLh_Ug5T0fWzWmj2Wc4QVRLL8hYCBCg5ucMLibGvpNCCKlGHpDocgE2kMJ7BUHylXVryzDukNvqqyLWk45wGbxFbX5Fcj/s1600/postgresql_ubuntu.png" /></a></div>
</td><td>Небольшой мануал по установке и настройке PostgreSQL сервера в Ubuntu (14.04).</td></tr>
</tbody></table>
<a name='more'></a><h3 style="text-align: left;">
1. Установка</h3>
Добавляем APT репозиторий:<br />
<div class="console">
$ echo 'deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main' \<br />
>> /etc/apt/sources.list.d/pgdg.list
</div>
Импортируем ключ:
<br />
<div class="console">
$ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | \<br />
sudo apt-key add -</div>
Устанавливаем:
<br />
<div class="console">
# sudo apt-get update
# apt-get install postgresql-{version}
</div>
Вместо {version} указываете ту версию сервера, которая вас интересует: 8.4 или 9.3.
<br />
<br />
Или выбираете более удобный для себя способ: <a href="http://www.postgresql.org/download/linux/ubuntu/">http://www.postgresql.org/download/linux/ubuntu/</a>.<br />
<h3 style="text-align: left;">
2. Конфигурирование</h3>
<div>
После установки рабочая директория поумолчанию <span style="font-family: 'Courier New', Courier, monospace;">/etc/postgresql/{version}/main/</span> (графический инсталятор производит установку в /opt/, тогда рабочая директория будет <span style="font-family: Courier New, Courier, monospace;">/opt/PostgreSQL-{version}/data</span>). В ней находятся основные конфигурационные файлы. </div>
<h4 style="text-align: left;">
Разрешаем соединения по TCP/IP</h4>
В конфигурационном файле <span style="font-family: Courier New, Courier, monospace;">postgresql.conf</span> (поумолчанию находится в <span style="font-family: Courier New, Courier, monospace;">/etc/postgresql/{version}/main/</span>) раскоментируем строку <span style="background-color: white; color: #333333; font-family: 'Ubuntu Mono', monospace; font-size: 14px; line-height: 22px; text-align: justify;">#listen_addresses = 'localhost' </span>удалив символ #.<br />
<div class="text">
listen_addresses = 'localhost'
</div>
Чтобы подключаться к серверу с других машин, значние <span style="background-color: white; color: #333333; font-family: 'Ubuntu Mono', monospace; font-size: 14px; line-height: 22px; text-align: justify;">'localhost'</span> надо заменить на IP адрес машины, или 0.0.0.0, или просто поставить <span style="font-family: Courier New, Courier, monospace;">'*'</span>.<br />
<div>
Все необходимые подробности вы можете найти в комментариях в конфигурационном файле.<br />
<h4 style="text-align: left;">
Настраиваем пользователя</h4>
</div>
<div>
При установке сервера в системе создается пользователь postgres с паролем postgres. С правами этого пользователя сервер общается с операционной системой (на сколько я понимаю). Но для работы вам этого не достаточно. Необходимо задать пароль для одноименного пользователя уже в рамках базы данных. Для этого запускаем консольный клиент psql с права пользователя postgres:</div>
<div class="console">
$ sudo -u postgres psql
</div>
И выполняем запрос:<br />
<div class="console">
ALTER USER postgres with encrypted password '_password_';
</div>
Где <span style="font-family: Courier New, Courier, monospace;">'_password_'</span> - пароль для подключения к серверу под пользователем postgres.<br />
<br />
<i>Если вы не смогли войти под пользователем postgres, потому что psql не соизволил принять пароль, решить проблему можно следующим образом:</i><br />
<ul style="text-align: left;">
<li><i>изменяете метод аутентификации для пользователя postgres на trust (см след. пункт)</i></li>
<li><i>изменяете пароль как описано выше</i></li>
<li><i>изменяете матод аутентификации на необходимый</i></li>
<li><i>не забываете перезапускать сервер после изменения метода аутентификации</i></li>
</ul>
<br />
<h4 style="text-align: left;">
Изменение метода аутентификации</h4>
В конфигурационном файле <span style="font-family: Courier New, Courier, monospace;">pg_hba.conf</span> (поумолчанию находится в <span style="font-family: Courier New, Courier, monospace;">/etc/postgresql/{version}/main/</span>) изменяем метод аутентификации для пользователя postgres c ident на md5:<br />
<div class="text">
<pre>local all postgres md5</pre>
</div>
После этого сервер необходимо перезапустить:<br />
<div class="console">
$ sudo /etc/init.d/postgresql restart</div>
<h3 style="text-align: left;">
3. Настраиваем pgAdmin</h3>
<blockquote class="tr_bq">
<b>pgAdmin</b> — наиболее популярная и многофункциональная открытая среда администрирования и разработки для <a href="http://www.postgresql.org/">PostgreSQL</a>, самой прогрессивной СУБД с открытым исходным кодом в мире. pgAdmin может работать на платформах Linux, FreeBSD, Solaris, Mac OSX и Windows и управлять PostgreSQL 7.3 и новее, а также коммерческими и производными версиями PostgreSQL, в частности <a href="http://www.enterprisedb.com/">Postgres Plus Advanced Server</a> и <a href="http://www.greenplum.com/">Greenplum</a>.<br />
pgAdmin создан для удовлетворения самых разных потребностей пользователей, от написания простых SQL-запросов до создания сложных баз данных. Графический интерфейс pgAdmin поддерживает все возможности PostgreSQL и значительно облегчает администрирование. Приложение включает SQL-редактор с подсветкой синтаксиса, редактор хранимых процедур, агент планирования заданий SQL/командной оболочки, поддержку механизма репликации <a href="http://slony.info/">Slony-I</a> и многое другое. Подключение к серверу можно установить, используя TCP/IP или Unix-сокеты (на платформах *nix ), и применить также SSL-шифрование для безопасности. При этом никакие дополнительные драйверы для соединения с сервером баз данных не требуются.<br />
pgAdmin разрабатывается сообществом экспертов PostgreSQL со всего мира и доступен более чем на десяти языках. Это свободное программное обеспечение выпускается под <a href="http://www.pgadmin.org/licence.php">лицензией PostgreSQL</a>.<br />
<div style="text-align: right;">
<a href="http://www.pgadmin.org/" target="_blank">http://www.pgadmin.org/ </a></div>
</blockquote>
<div>
Если установка сервера производилась из репозитория, то установить графический клиент можно командой: </div>
<div class="console">
$ sudo apt-get install pgadmin3
</div>
<ol style="text-align: left;">
<li>Запускаем pgAdmin:
<div class="console">
$ pgadmin3
</div>
</li>
<li>Создаем новое подключение к серверу: <span style="font-family: Courier New, Courier, monospace;">File->Add server</span></li>
<li>Даем подключению имя, указываем в качестве хоста localhost, в качестве пользователя postgres с паролем, созданным при конфигурировании сервера</li>
<li>Если все хорошо сконфигурировано, проблем возникнуть не должно. </li>
</ol>
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc-UaRkuqsfiSXRX5OCeF6iUbJ_NjwE51-pMem1Q04TPh-J3MFB2-jSY8AQ4CYCNlMP4W5tIDEc_buvkymFA976HmmOGOzIZpKYmkx-fuWFIQry5FlVsDX6soIzqLhRg9LGunR58R2NfOP/s1600/Screenshot+from+2014-09-04+18:06:26.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc-UaRkuqsfiSXRX5OCeF6iUbJ_NjwE51-pMem1Q04TPh-J3MFB2-jSY8AQ4CYCNlMP4W5tIDEc_buvkymFA976HmmOGOzIZpKYmkx-fuWFIQry5FlVsDX6soIzqLhRg9LGunR58R2NfOP/s1600/Screenshot+from+2014-09-04+18:06:26.png" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8aiFz7UKsICeOEfav8SsjFam3jxjahhvvxhpS1OBJZ0683l0WQrtInjvgnGNeY6x19yGo9rsaHp9IgkpUhY6fHbzv8fMm9qcU6YxVLDXzPdSSUIwkEm6UGOiBOcHlOfq6ntvZSH-Q5LXc/s1600/Screenshot+from+2014-09-04+18:08:55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8aiFz7UKsICeOEfav8SsjFam3jxjahhvvxhpS1OBJZ0683l0WQrtInjvgnGNeY6x19yGo9rsaHp9IgkpUhY6fHbzv8fMm9qcU6YxVLDXzPdSSUIwkEm6UGOiBOcHlOfq6ntvZSH-Q5LXc/s1600/Screenshot+from+2014-09-04+18:08:55.png" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtiXtsKSXUzMPV2zG56OMdhFYXxlxPJd9xQcwXDFPLkA-6FlZt-2BMen3g61EBJgPKZYtMmG953NJO7ndIuppnR6urgIm72gPsEex9vIMlmsXiYD90IgrsbX5f7SIqqq3zIpyb1ZH80KfM/s1600/Screenshot+from+2014-09-04+18:11:05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtiXtsKSXUzMPV2zG56OMdhFYXxlxPJd9xQcwXDFPLkA-6FlZt-2BMen3g61EBJgPKZYtMmG953NJO7ndIuppnR6urgIm72gPsEex9vIMlmsXiYD90IgrsbX5f7SIqqq3zIpyb1ZH80KfM/s1600/Screenshot+from+2014-09-04+18:11:05.png" /></a>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWzmG8isBW4Sh2dm9AEIa4204jEt5Hzj3MorUz1n3IBN8uyi-A8Tfow86njJI-Gi-buP2E61B_iDXvJkEExIdxe8jegqNkhhoW1jOeiBaSXhIB20-9wbDYd3yODEvWJhqwSFhHFsb1xEWZ/s1600/Screenshot+from+2014-09-04+18:13:19.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWzmG8isBW4Sh2dm9AEIa4204jEt5Hzj3MorUz1n3IBN8uyi-A8Tfow86njJI-Gi-buP2E61B_iDXvJkEExIdxe8jegqNkhhoW1jOeiBaSXhIB20-9wbDYd3yODEvWJhqwSFhHFsb1xEWZ/s1600/Screenshot+from+2014-09-04+18:13:19.png" /></a>
<br />
<h3 style="text-align: left;">
4. Дополнительные источники информации</h3>
Самый исчерпывающий ресурс: <a href="http://www.postgresql.org/docs/" target="_blank">Документация</a>. Может быть установлена локально:
<br />
<div class="console">
sudo apt-get install postgresql-doc-{version}
</div>
После установки находится здесь: <a href="file:///usr/share/doc/postgresql-doc-8.4/html/index.html" target="_blank">file:///usr/share/doc/postgresql-doc-{version}/html/index.html </a></div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com21tag:blogger.com,1999:blog-7407396207758383724.post-86750540163721088252014-09-01T15:54:00.000+04:002016-04-05T14:52:34.757+03:00Удобные алиасы для git<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFX2EuPUKmBNTAb-T5Z71kjz4ZFRYcWYz33RIjHsJqDKWU-_Y1bCRp0TLFRGB2gZOFtYL76G-zPzr4LhBqotT-vo7d6l0DLJ2JSguKa_zbWiMpPguEVmmVljcn7JCul6VSI-SDftK7BGoj/s1600/gitalias.png" imageanchor="1"><img border="0" height="176" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFX2EuPUKmBNTAb-T5Z71kjz4ZFRYcWYz33RIjHsJqDKWU-_Y1bCRp0TLFRGB2gZOFtYL76G-zPzr4LhBqotT-vo7d6l0DLJ2JSguKa_zbWiMpPguEVmmVljcn7JCul6VSI-SDftK7BGoj/s320/gitalias.png" width="200" /></a>
</td><td>Добавить в файл $HOME/.gitconfig
<br />
<div class="code">
[alias]<br />
co = checkout<br />
st = status -sb<br />
ci = commit<br />
br = branch<br />
hist = log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short<br />
type = cat-file -t<br />
dump = cat-file -p<br />
l = log --pretty="%Cgreen%cn%Creset %C(auto)%h%Creset %Cred%ad%Creset%n %s" --date=relative<br />
lg = log --pretty="%Cgreen%cn%Creset %C(auto)%h%Creset %Cred%ad%Creset%n %s" --date=relative --graph<br />
forget = update-index --assume-unchanged<br />
recall = update-index --no-assume-unchanged<br />
<a href="http://habrahabr.ru/post/155391/" target="_blank">in = '!git remote update -p; git log ..@{u}'</a><br />
<a href="http://habrahabr.ru/post/155391/" target="_blank">out = 'log @{u}..'</a></div>
</td></tr>
</tbody></table>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-55074556814239329692014-08-30T16:16:00.000+04:002015-04-22T20:00:23.852+03:00Простое RESTful приложение с помощью JAX-RS<div dir="ltr" style="text-align: left;" trbidi="on">
<table>
<tbody>
<tr valign="top">
<td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlw3FgYe7X8tzONVBoc_w611qZnfnS1CT8dj527nGSL9PoF0xzO-xkBKzJ4ZIGne5W0T0bWMvzJqoogzRRIhFLB_VneEdxfj83AVgDShlnda1GRcqEfyes7GjyZbDMHaDgDep2fBr5GNqI/s1600/restful.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlw3FgYe7X8tzONVBoc_w611qZnfnS1CT8dj527nGSL9PoF0xzO-xkBKzJ4ZIGne5W0T0bWMvzJqoogzRRIhFLB_VneEdxfj83AVgDShlnda1GRcqEfyes7GjyZbDMHaDgDep2fBr5GNqI/s1600/restful.png" height="150" width="130" /></a></div>
<br /></td>
<td>RESTful - что может быть проще? Только RESTful реализованный с помощью Java EE! <br />
<br />
Конечно, многие с этим не согласятся и приведут кучу весомых аргументов против, и будут по своему правы. Но
поверьте, в Java мире проделана огромная работа, чтобы упростить воплощение ваших REST идей в жизнь. И в
этой статье мне бы хотелось наглядно это продемонстрировать.<br />
<br /></td>
</tr>
</tbody>
</table>
<a name='more'></a><script src="http://www.onicos.com/staff/iz/amuse/javascript/expert/deflate.txt"></script>
<script>
function encode64(data) {
r = "";
for (i=0; i<data.length; i+=3) {
if (i+2==data.length) {
r +=append3bytes(data.charCodeAt(i), data.charCodeAt(i+1), 0);
} else if (i+1==data.length) {
r += append3bytes(data.charCodeAt(i), 0, 0);
} else {
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i+1), data.charCodeAt(i+2));
}
}
return r;
}
function append3bytes(b1, b2, b3) {
c1 = b1 >> 2;
c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
c3 = ((b2 & 0xF) << 2) | (b3 >> 6);
c4 = b3 & 0x3F;
r = "";
r += encode6bit(c1 & 0x3F);
r += encode6bit(c2 & 0x3F);
r += encode6bit(c3 & 0x3F);
r += encode6bit(c4 & 0x3F);
return r;
}
function encode6bit(b) {
if (b < 10) {
return String.fromCharCode(48 + b);
}
b -= 10;
if (b < 26) {
return String.fromCharCode(65 + b);
}
b -= 26;
if (b < 26) {
return String.fromCharCode(97 + b);
}
b -= 26;
if (b == 0) {
return '-';
}
if (b == 1) {
return '_';
}
return '?';
}
</script>
<script type='text/javascript'>
$(document).ready(function(){
$('uml').each(function() {
element = $(this);
code = element.html()
code = code.replace(new RegExp(">",'g'), ">");
code = code.replace(new RegExp("<",'g'), "<");
code = unescape(encodeURIComponent(code));
src = "'http://www.plantuml.com/plantuml/img/"+encode64(zip_deflate(code, 9)) + "'";
element.replaceWith("<div style='overflow: auto;'><img src="+src+"/></div>");
});
});
</script>
Скажу сразу, статья не затрагивает теорию Rest-приложений, ее вы и так можете самостоятельно почерпнуть из
множества статей на просторах интернета. Приведу лишь ссылку на цикл статей от Mugunth Kumar, достойных внимания: <a href="http://blog.mugunthkumar.com/articles/restful-api-server-doing-it-the-right-way-part-1/">RESTful API
Server – Doing it the right way</a> (перевод на хабре: <a href="http://habrahabr.ru/post/144011/">RESTful API для
сервера – делаем правильно</a>) и статью <a href="http://seclgroup.ru/article_rest_api_web.html">Как мы делали API
для социальной сети (REST API для WEB)</a> подробно освещающую основные аспекты идеологии REST.<br />
<br />
Итак, самый простой способ объяснить что-то - показать это "что-то" в деле. Для примера, будет показан процесс
реализации REST API для простого планировщика повседневных задач - так называемого TODO листа.<br />
<br />
<h3 style="text-align: left;">
Что из себя представляет приложение TODO?</h3>
<div>
Демонстрационное приложение TODO будет позволять вести список задач. Каждая задача будет состоять из уникального
идентификатора, имени, описания и статуса выполнена/невыполнена:
</div>
<uml style="display: none;">
class Task {
uuid: UUID
name: String
description: String
completed: boolean
}
</uml>
<br />
Задачи будут представлены с помощью json. Пример Json представления задачи:
<br />
<div class="text">
<pre>{
"uuid":"550e8400-e29b-41d4-a716-446655440000",
"name":"написать RESTful приложение",
"description":"чтобы полученные знания принесли пользу и не были забыты, ими необходимо воспользоваться в течении первых 72 часов после получения",
"completed":false
}</pre>
</div>
В качестве значения completed допускается true или false.
<br />
<br />
API приложения будет предусматривать следующие действия:<br />
<br />
<uml style="display: none;">
usecase GET as "<b>Получить список всех заданий</b>
--
GET: /tasks"
usecase GET_ACTIVE as "<b>Получить список выполненных/невыполненных заданий</b>
--
GET: /tasks/find?completed=false{true}"
usecase GET_BY_ID as "<b>Получить конкретное задание</b>
--
GET: /tasks/{id}"
usecase POST as "<b>Добавить задание</b>
--
POST: /tasks"
usecase PUT as "<b>Создание нового задания/изменение существующего</b>
--
PUT: /tasks/{id}"
usecase PATCH as "<b>Редактировать отдельные параметры заданий</b>
--
PATCH: /tasks/{id}"
usecase DELETE as "<b>Удалить задание</b>
--
DELETE: /tasks/{id}"
User -u-> (GET)
User -u-> (GET_ACTIVE)
User -l-> (GET_BY_ID)
User -r-> (PUT)
User --> (POST)
User --> (PATCH)
User --> (DELETE)
</uml>
<br />
<ul style="text-align: left;">
<li><span class="pointer" onclick="$('#li_1').toggle();">Получение списка всех заданий</span>
<div id="li_1" style="display: none;">
<ul>
<li>GET: /tasks</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#200">200
OK</a></li>
</ul>
Возвращает массив объектов Task в формате json:
<br />
<div class="text">
<pre>[
{"uuid":"", "name":"", "description":"", "completed":""}
{"uuid":"", "name":"", "description":"", "completed":""}
...
]</pre>
</div>
</div>
</li>
<li><span class="pointer" onclick="$('#li_2').toggle();">Получение списка выполненых/невыполненных заданий</span>
<div id="li_2" style="display: none;">
<ul>
<li>GET: /tasks/find?completed=false/true</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#200">200
OK</a></li>
</ul>
Возвращает массив объектов Task в формате json:
<br />
<div class="text">
<pre>[
{"uuid":"", "name":"", "description":"", "completed":""}
{"uuid":"", "name":"", "description":"", "completed":""}
...
]</pre>
</div>
</div>
</li>
<li><span class="pointer" onclick="$('#li_3').toggle();">Получение конкретного задания</span>
<div id="li_3" style="display: none;">
<ul>
<li>GET: /tasks/{id}</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#200">200
OK</a></li>
</ul>
Возвращает задачу в формате json:
<br />
<div class="text">
<pre>{
"uuid":"",
"name":"",
"description":"",
"completed":""
}</pre>
</div>
</div>
</li>
<li><span class="pointer" onclick="$('#li_4').toggle();">Добавление задания</span>
<div id="li_4" style="display: none;">
<ul>
<li>POST: /tasks/</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#201">201
Created</a></li>
</ul>
Добавляет в систему новую задачу, переданную в теле запроса в json формате:
<br />
<div class="text">
<pre>{
"name":"",
"description":""
}</pre>
</div>
Значение свойства completed при этом становится false. <br />
В заголовке ответа location возвращает URL созданной задачи
<br />
<div class="text">
<pre>Location: http://www.todo.ru/tasks/{new uuid}
</pre>
</div>
Повторный вызов метода приведет к созданию еще одной задачи с таким же именем и описанием, но новым uuid.
</div>
</li>
<li><span class="pointer" onclick="$('#li_5').toggle();">Создание нового задания/изменение существующего</span>
<div id="li_5" style="display: none;">
<ul>
<li>PUT</li>
<li>URI: /tasks/{id}</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#201">201
Created</a>/<a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#204">204
No Content</a></li>
</ul>
Принимает в теле запроса описание задания в формате
<br />
<div class="text">
<pre>{
"name":"",
"description":"",
"completed":""
}</pre>
</div>
Если задания с идентификатором, указанном в URI не существовало, оно будет создано. Код ответа в этом случае
201. Если задание с таким идентификатором уже существовало, значения его свойств будут изменены на
переданные в
запросе. Код ответа в случае успеха 204.
</div>
</li>
<li><span class="pointer" onclick="$('#li_6').toggle();">Редактирование отдельных параметров задания</span>
<div id="li_6" style="display: none;">
<ul>
<li>PATCH: /tasks/{id}
</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#204">204
No Content</a></li>
</ul>
Принимает в теле запроса частичное описание задания в формате json. Пример:
<br />
<div class="text">
<pre>{
"completed":"true"
}</pre>
</div>
Изменяет значения только тех свойств, которые перечислены в параметре запроса. В случае успеха возвращает
код 204.
</div>
</li>
<li><span class="pointer" onclick="$('#li_7').toggle();">Удаление задания</span>
<div id="li_7" style="display: none;">
<ul>
<li>DELETE: /tasks/{id}</li>
<li>Ответ в случае успеха: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#204">204
No Content</a></li>
</ul>
Если задания с указанным идентификатором не удастся найти, все равно вернет ответ с кодом 204.
</div>
</li>
</ul>
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw9qbfyvN_sZq8NxK55kvs2JTCIQpR_p7YxBFedd_XRV2MeWYwvFcieUWFxS0v4eVhDvFn1BRnDCxtq2ash7b6LiF8fqij3DE2W8OVcLCR-3TWZOP0sD8pazEPejYf4WHv8l8oTIpu6mz0/s1600/82941400618.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjw9qbfyvN_sZq8NxK55kvs2JTCIQpR_p7YxBFedd_XRV2MeWYwvFcieUWFxS0v4eVhDvFn1BRnDCxtq2ash7b6LiF8fqij3DE2W8OVcLCR-3TWZOP0sD8pazEPejYf4WHv8l8oTIpu6mz0/s1600/82941400618.jpg" height="200" width="95" /></a>Пример
специально максимально упрощен, чтобы не отвлекать читателя от API предоставляемой J2EE и быстро создать общую картину
процесса реализации RESTful приложений на java.<br />
<br />
<h3 style="text-align: left;">
Функциональные тесты</h3>
Статья не затрагивает тему написания клиента для разрабатываемого api. Вместо этого я предлагаю решение в виде функциональных тестов, написанное с помощью JUnit, Apache HttpComponents и Google gson.<br />
<div class="code" id="ft">
<pre><code class="java">package ru.dokwork.todo;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.HttpResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.UUID;
import static ru.dokwork.todo.ResponseChecker.assertThat;
public class TaskServiceFT {
private static final String URL = "http://localhost:8080/todo/tasks/";
static final JsonObject jtask1;
static final JsonObject jtask2;
JsonParser parser;
TaskServiceClient todo;
static {
jtask1 = new JsonObject();
jtask1.addProperty("name", "First task");
jtask1.addProperty("description", "First task for test.");
jtask1.addProperty("completed", false);
jtask2 = new JsonObject();
jtask2.addProperty("name", "Second task");
jtask2.addProperty("description", "Second task for test.");
jtask2.addProperty("completed", false);
}
@Before
public void setup() throws Exception {
parser = new JsonParser();
todo = new TaskServiceClient(URL);
todo.removeAllExistingTasks();
}
@Test
public void testGetTask() throws Exception {
// Add task
HttpResponse response = todo.addNewTask(jtask1);
// Get uuid
String uuid = response.getFirstHeader("location").getValue().substring(URL.length());
// Get task
response = todo.getTask(UUID.fromString(uuid));
// Create expected json
JsonObject expectedObject = createJsonTask(uuid, jtask1);
// Check answer
assertThat(response).haveCode(200).haveContent(expectedObject);
}
@Test
public void testGetAllTasks() throws Exception {
// Add first task
HttpResponse response = todo.addNewTask(jtask1);
String uuid1 = response.getFirstHeader("location").getValue().substring(URL.length());
// Add second task
response = todo.addNewTask(jtask2);
String uuid2 = response.getFirstHeader("location").getValue().substring(URL.length());
// Get all tasks
response = todo.getAll();
// Create expected array
JsonArray array = new JsonArray();
array.add(createJsonTask(uuid1, jtask1));
array.add(createJsonTask(uuid2, jtask2));
// Check answer
assertThat(response).haveCode(200).haveContent(array);
}
@Test
public void testAddTask() throws Exception {
// Add task
HttpResponse response = todo.addNewTask(jtask1);
// Check answer
assertThat(response).haveCode(201).haveHeaders("location", 1);
// Get uuid
String uuid = response.getFirstHeader("location").getValue().substring(URL.length());
// Check new task
response = todo.getTask(UUID.fromString(uuid));
JsonObject addedTask = createJsonTask(uuid, jtask1);
assertThat(response).haveContent(addedTask);
}
@Test
public void testPutNewTask() throws Exception {
// Put new task
UUID uuid = UUID.randomUUID();
HttpResponse response = todo.putTask(uuid, jtask1);
// Check answer
assertThat(response).haveCode(201).haveHeaders("location", 1);
response.getFirstHeader("location").getValue().endsWith(uuid.toString());
// Check new task
response = todo.getTask(uuid);
JsonObject addedTask = createJsonTask(uuid.toString(), jtask1);
assertThat(response).haveContent(addedTask);
}
@Test
public void testPutExistingTask() throws Exception {
// Add task
HttpResponse response = todo.addNewTask(jtask1);
// Get uuid
String uuid = response.getFirstHeader("location").getValue().substring(URL.length());
// Change task
JsonObject editedTask = createJsonTask(uuid, jtask1);
editedTask.addProperty("name", "Edited task");
// Put edited task
response = todo.putTask(UUID.fromString(uuid), editedTask);
// Check answer
assertThat(response).haveCode(204).haveHeaders("location", 1);
response.getFirstHeader("location").getValue().endsWith(uuid.toString());
// Check edited task
response = todo.getTask(UUID.fromString(uuid));
assertThat(response).haveContent(editedTask);
}
@Test
public void testPatchTask() throws Exception {
// Add task
HttpResponse response = todo.addNewTask(jtask1);
// Get uuid
String uuid = response.getFirstHeader("location").getValue().substring(URL.length());
// Create patch
JsonObject patch = new JsonObject();
patch.addProperty("completed", true);
// Patch state for task
response = todo.patch(UUID.fromString(uuid), patch);
// Check answer
assertThat(response).haveCode(204).haveHeaders("location", 1);
response.getFirstHeader("location").getValue().endsWith(uuid);
// Check patched task
response = todo.getTask(UUID.fromString(uuid));
// Create expected json
JsonObject expectedObject = createJsonTask(uuid, jtask1);
expectedObject.addProperty("completed", true);
// Check task
assertThat(response).haveContent(expectedObject);
}
@Test
public void testFindTasks() throws Exception {
// Add first task
HttpResponse response = todo.addNewTask(jtask1);
// Get uuid for first task
String uuid1 = response.getFirstHeader("location").getValue().substring(URL.length());
// Get uuid for second task
String uuid2 = UUID.randomUUID().toString();
// Add second task
JsonObject jsonTask2 = createJsonTask(uuid2, jtask2);
jsonTask2.addProperty("completed", true);
todo.putTask(UUID.fromString(uuid2), jsonTask2);
// Get only completed tasks
response = todo.find(true);
// Create expected content
JsonArray expectedContent = new JsonArray();
expectedContent.add(createJsonTask(uuid2, jsonTask2));
// Check answer
assertThat(response).haveCode(200).haveContent(expectedContent);
}
@Test
public void testDeleteTask() throws Exception {
// Add task
HttpResponse response = todo.addNewTask(jtask1);
// Get uuid
String uuid = response.getFirstHeader("location").getValue().substring(URL.length());
// Delete task
int countBefore = todo.getTasksCount();
response = todo.delete(UUID.fromString(uuid));
// Check answer
assertThat(response).haveCode(204);
int countAfter = todo.getTasksCount();
Assert.assertEquals(1, countBefore - countAfter);
}
private JsonObject createJsonTask(String uuid, JsonObject pattern) {
JsonObject expectedObject = new JsonObject();
expectedObject.add("uuid", parser.parse(uuid));
expectedObject.add("name", pattern.get("name"));
expectedObject.add("description", pattern.get("description"));
expectedObject.add("completed", pattern.get("completed"));
return expectedObject;
}
}
</code></pre>
</div>
<br />
Классы <span style="font-family: Courier New, Courier, monospace;">ResponseChecker</span> и <span style="font-family: Courier New, Courier, monospace;">TaskServiceClient</span> призваны сделать код функциональных тестов более наглядным.<br />
<span class="pointer" onclick="$('#ResponseChecker').toggle();">ResponseChecker</span>
<br />
<div id="ResponseChecker" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/4d8e857ee4f96891a884f3184cf8552e153b8dac/src/ft/java/ru/dokwork/todo/ResponseChecker.java?embed=t"></script>
</div>
<br />
<span class="pointer" onclick="$('#TaskServiceClient').toggle();">TaskServiceClient</span>
<br />
<div id="TaskServiceClient" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/807d380589f55404247aaeaa8a28d73bfcbf3b59/src/ft/java/ru/dokwork/todo/TaskServiceClient.java?embed=t"></script>
</div>
<br />
Обратите внимание на метод PATCH. Он не реализован (на момент написания статьи) в библиотеке Apache HttpComponents и его реализацией необходимо заняться самостоятельно. К счастью это совершенно не трудно:<br />
<div class="code">
<pre><code class="java">
public class HttpPatch extends HttpPost {
@Override
public String getMethod() {
return "PATCH";
}
public HttpPatch() {
super();
}
public HttpPatch(URI uri) {
super(uri);
}
public HttpPatch(String url) {
super(url);
}
}
</code></pre>
</div>
<i>Не смотря на то, что для написания тестов использовалась библиотека JUnit, это совсем не модульные тесты. Поэтому не стоит их складывать в каталог test, а лучше создать для этих целей отдельную директорию.</i><br />
<h3 style="text-align: left;">
Введение в JAX-RS API</h3>
Каждому ресурсу в rest-приложениях соответствует некоторый URI, а жизненный цикл ресурса управляется с помощью методов
GET, POST, PUT и DELETE http протокола. JAX-RS предлагает очень прозрачную схему реализации такой идеи. Вы просто
заводите сервисный класс, отвечающий за некоторую сущность и указываете базовый URL для этой сущности:<br />
<div class="code">
<pre><code class="java">
@Path(value="/tasks")
class TaskService {
}
</code></pre>
</div>
<br />
Затем реализуете методы для управления этими сущностями и связываете их с методами http протокола:<br />
<div class="code">
<pre><code class="java">
@Path(value="/tasks")
class TaskService {
@GET
public List<Task> getTasks() {
}
@POST
public void addTask(Task task) {
}
}
</code></pre>
</div>
просто, не так ли? На самом деле не совсем. Чудес не бывает, и никто не сможет за вас решить, в каком формате передавать
ваши данные в ответ на GET запрос и откуда брать параметры для POST запроса. Все это необходимо реализовывать
самостоятельно. Хорошая новость в том, что за вас о многом уже позаботились. <br />
<br />
Так,из коробки, вы получаете возможность сериализации/десериализации данных в популярные форматы, такие как JSON и XML с
помощью аннотации<br />
<div class="code">
<pre><code class="java">@Produces({"application/xml", "application/json"})
</code></pre>
</div>
Аннотация может быть применена как к каждому конкретному методу в отдельности, так и ко всему сервисному классу в целом.
Конкретный тип представления данных выбирается исходя из указанного в запросе MIME типа, или первый, если в запросе
также перечислены оба. Полный перечень типов вы можете найти на страницах спецификации: <a href="http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/core/MediaType.html">http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/core/MediaType.html</a>
<br />
<br />
Что касается вопроса, откуда брать аргументы метода, то тут дефицита в вариантах нет. Возможными источниками могут
выступать: параметры http запроса, значения веб-форм, часть пути url, значения куки, заголовки запроса и матрицы.
Чтобы указать конкретный источник, вы должны пометить аргумент одной из аннотаций:<br />
<ul style="text-align: left;">
<li>@FormParam</li>
<li>@PathParam</li>
<li>@QueryParam</li>
<li>@MatrixParam</li>
<li>@HeaderParam</li>
<li>@CookieParam</li>
</ul>
В этой статье я затрону только первые три из них. Более подробную информацию вы всегда можете получить со страниц сайта
Oracle: <a href="http://docs.oracle.com/javaee/6/tutorial/doc/gilik.html">http://docs.oracle.com/javaee/6/tutorial/doc/gilik.html</a>.<br />
<br />
<b>@FormParam</b> привязывает значения параметров формы к значениям параметров метода:<br />
<div class="code">
<pre><code class="java">
@POST
@Path("/")
public Task addTask(@FormParam("name") String name,
@FormParam("description") String description) {
}
</code></pre>
</div>
<br />
При большом количестве аргументов конструкция метода выглядит устрашающе. К счастью есть возможность этого избежать. Для
этого вы можете повесить аннотации @FormParam прямо на поля класса и сделать этот класс аргументом метода:<br />
<div class="code">
<pre><code class="java">class Task {
@FormParam("name")
String name;
@FormParam("description")
String description;
}
@Path(value="/tasks")
class TaskService {
@POST
@Path("/")
public Task addTask(@Form Task task) {
}
}</code></pre>
</div>
<br />
Важный нюанс такого решения: класс, с аннотированными полями, должен содержать конструктор поумолчанию!<br />
<br />
<b>@PathParam</b> позволяет использовать в качестве аргументов часть пути:<br />
<div class="code">
<pre><code class="java">
@GET
@Path("/{id}")
public Task getTask(@PathParam("id") String id) {
}
</code></pre>
</div>
<br />
<b>@QueryParam</b> позволяет использовать в качестве аргументов параметры HTTP запроса:<br />
<div class="code">
<pre><code class="java">
@GET
@Path("/task?id=123")
public Task getTask(@QueryParam("id") String id) {
}
</code></pre>
</div>
<br />
URL для вызова такого метода может выглядеть так: http://todo.com/tasks/task?id=123<br />
<br />
Чтобы избежать NullPointerException в случае, если в запросе будут пропущенные параметры, можно указать для аргумента
значение поумолчанию:<br />
<div class="code">
<pre><code class="java">
@GET
@Path("/task")
public Task getTask(@DefaultValue("123") @QueryParam("id") String id) {
}
</code></pre>
</div>
<br />
В качестве типов аргументов помеченных как @QueryParam или @PathParam могут выступать:
<complete id="goog_526451018"></complete>
<br />
<ul style="text-align: left;">
<li>Все примитивные типы кроме char</li>
<li>Все классы-оболочки кроме Character</li>
<li>Любой класс с конструктором, который принимает единственный аргумент типа String </li>
<li>Любой класс со статическим методом valueOf(String)</li>
<li>List<T>, Set<T>, или SortedSet<T>, где T - тип удовлетворяющий условиям выше</li>
</ul>
<div style="text-align: left;">
Но примитивными типами аргументы методов не ограничиваются. Выше уже было показано как с помощью параметров формы
принимать в методах целые классы. Или как легко превращать возвращаемые методами объекты в json или xml
представление. Справедливо было бы ожидать возможность так же легко и получать объекты, созданные из json или xml
запроса. И такая возможность есть! Для этого достаточно пометить метод аннотацией @Consumes и указать принимаемый
медиа тип:
</div>
<div class="code">
<pre><code class="java">
@POST
@Consumes(MediaType.APPLICATION_JSON)
public void updateTask(Task task) {
}
</code></pre>
</div>
Такой метод будет вызван, если в заголовке запроса будет указан соответствующий тип:
<br />
<div class="code">
POST /contacts HTTP/1.1<br />
Content-Type: application/json<br />
Content-Length: 32
</div>
<h3 style="text-align: left;">
Привет мир!</h3>
<div>
И так, общее представление о JAX-RS уже должно было сложиться. Осталось опробывать его в деле. Как и у любой
спецификации в мире java, у JAX-RS есть несколько реализаций:<br />
<div style="text-align: left;">
</div>
<ul style="text-align: left;">
<li><a href="http://en.wikipedia.org/wiki/Apache_CXF">Apache CXF</a></li>
<li><a href="http://jersey.java.net/">Jersey</a></li>
<li><a href="http://www.jboss.org/resteasy" rel="nofollow">RESTeasy</a></li>
<li><a href="http://en.wikipedia.org/wiki/Restlet">Restlet</a></li>
<li><a href="http://en.wikipedia.org/wiki/Apache_Wink">Apache Wink</a></li>
<li><a href="http://en.wikipedia.org/wiki/WebSphere_Application_Server">WebSphere Application Server</a>:<span style="line-height: 1.6;">Version 7.0, </span><span style="line-height: 1.6;">Version 8.0</span>
</li>
<li><a href="http://en.wikipedia.org/wiki/Oracle_WebLogic_Server">WebLogic Application Server</a></li>
<li><a href="http://tuscany.apache.org/documentation-2x/sca-java-bindingrest.html">Apache Tuscany</a></li>
<li><a href="http://www.cuubez.com/">Cuubez framework</a></li>
</ul>
По ряду исключительно субъективных причин, я остановлю свой выбор на RESTeasy от Red Hat - реализации используемой в
JBoss. Соответственно в качестве сервера приложения будет использоваться JBoss версии 8, носящий гордое имя <a href="http://wildfly.org/">Wildfly</a>.
</div>
<div>
<br />
О процессе инсталяции сервера я рассказывать не стану и буду ожидать, что с этим вы справитесь самостоятельно.
Поэтому сразу перейду к разработке.
</div>
<div>
<br />
Создадим пустой maven проект:
</div>
<div class="console">
mvn archetype:generate -DgroupId=ru.dokwork -DartifactId=todo -DinteractiveMode=false
</div>
<div>
Удалим лишние классы, которые были созданы автоматически, и настроим скрипт:<br />
Освежим версию java:<br />
<div class="code">
<pre><code class="xml"> <plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin></code></pre>
</div>
Настроим плагин для сборки war архивов:<br />
<div class="code">
<pre><code class="xml"> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
</plugin></code></pre>
</div>
Упростим себе жизнь, настроив процесс локального разворачивания приложения прямо из скрипта (при работе из IDE это
может упростить жизнь, но я не настаиваю):<br />
<div class="code">
<pre><code class="xml"> <plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>1.0.2.Final</version>
<configuration>
<hostname>127.0.0.1</hostname>
<port>9990</port>
<username>admin</username>
<password>password</password>
</configuration>
</plugin></code></pre>
</div>
Пользоваться плагином достаточно просто:<br />
<div class="console">
<pre>mvn wildfly:deploy
mvn wildfly:redeploy
mvn wildfly:undeploy</pre>
</div>
Думаю имена команд говорят сами за себя и разъяснений не требуют. Более подробную информацию вы можете найти здесь:
<a href="https://docs.jboss.org/wildfly/plugins/maven/latest/usage.html">https://docs.jboss.org/wildfly/plugins/maven/latest/usage.html</a><br />
<br />
Теперь добавим зависимость RESTeasy:<br />
<div class="code">
<pre><code class="xml"> <dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.0.8.Final</version>
<scope>provided</scope>
</dependency></code></pre>
</div>
И чтобы получить возможность преобразовывать возвращаемые объекты в json добавим реализацию соответствующего
провайдера:<br />
<div class="code">
<pre><code class="xml"> <dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>3.0.8.Final</version>
<scope>provided</scope>
</dependency></code></pre>
</div>
<br />
Ну и наконец, добавим библиотеки для тестирования:<br />
<br />
<ul style="text-align: left;">
<li>Библиотека для модульного тестирования</li>
</ul>
<div class="code">
<pre><code class="xml"> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency></code></pre>
</div>
<ul style="text-align: left;">
<li>Библиотеки для работы с http:</li>
</ul>
<div class="code">
<pre><code class="xml"> <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.3.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
<scope>test</scope>
</dependency></code></pre>
</div>
<ul style="text-align: left;">
<li>И библиотека для преобразования объектов из json:</li>
</ul>
<div class="code">
<pre><code class="xml"> <dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
<scope>test</scope>
</dependency></code></pre>
</div>
<span class="pointer" onclick="$('#pom').toggle();">pom.xml</span>
<br />
<div id="pom" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/4c39ccd3caa645a630baf2c30d906418beb2966c/pom.xml?embed=t"></script>
</div>
<br />
Там где сервлеты, там и web.xml. Согласно принятой в maven структуре каталогов, этот файл должен лежать в
директории src/main/webapp/WEB-INF/web.xml:<br />
<div class="code">
todo<br />
├── pom.xml<br />
└── src<br />
├── main<br />
│ ├── java<br />
│ │ └── ru<br />
│ │ └── dokwork<br />
│ │ └── todo<br />
│ └── webapp<br />
│ └── WEB-INF<br />
│ └── web.xml<br />
└── test<br />
└── java
</div>
<br />
Для включения библиотеки resteasy в web.xml указывается слушатель и специальный сервлет:<br />
<div class="code">
<pre><code class="xml">
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
</listener-class>
</listener>
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet></code></pre>
</div>
Далее указывается какие адреса должен обрабатывать сервлет:<br />
<div class="code">
<pre><code class="xml">
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping></code></pre>
</div>
Если url-pattern отличается от /*, вы должны явно указать используемый resteasy.servlet.mapping.prefix:<br />
<div class="code">
<pre><code class="xml">
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/resteasy/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/resteasy</param-value>
</context-param></code></pre>
</div>
Все сервисные классы должны быть явно перечислены в этом конфигурационном файле. Но к счастью есть возможность
избежать этого с помощью автоматического сканирования:<br />
<div class="code">
<pre><code class="xml">
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param></code></pre>
</div>
<br />
<span class="pointer" onclick="$('#web').toggle();">web.xml</span>
<br />
<div id="web" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/4c39ccd3caa645a630baf2c30d906418beb2966c/src/main/webapp/WEB-INF/web.xml?embed=t"></script>
</div>
<br />
Приложение для хранения списка задач так и напрашивается на использование базы данных, но использование баз данных всегда сопрежено с мелкими нюансами, отвлекаться на которые в этой статье совершенно не хочется. Поэтому я отгорожусь от задачи хранения заданий с помощью интерфейса некоторого DAO класса:<br />
<uml style="display: none;">
interface Task {
getByUUID(uuid: UUID): Task
save(task: Task)
remove(task: Task)
findByStatus(isCompleted: boolean) Collection<Task>
getAll(): Collection<Task>
}
</uml>
<br />
И воспользуюсь простейшей его реализацией:<br />
<span class="pointer" onclick="$('#MockTaskDao').toggle();">MockTaskDao</span>
<br />
<div id="MockTaskDao" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/14705e545b7590d1d8fcbd5edad3412b969c8fe7/src/main/java/ru/dokwork/todo/dao/MockTaskDao.java?embed=t"></script>
</div>
<br />
Само же приложение будет состоять из класса описывающего задачу и сервиса, эти задачи обслуживающего:<br />
<span class="pointer" onclick="$('#Task').toggle();">Task</span>
<br />
<div id="Task" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/4d8e857ee4f96891a884f3184cf8552e153b8dac/src/main/java/ru/dokwork/todo/Task.java?embed=t"></script>
</div>
<br />
<span class="pointer" onclick="$('#TaskService').toggle();">TaskService</span>
<br />
<div id="TaskService" style="display: none;">
<script src="https://bitbucket.org/dok/todo/src/807d380589f55404247aaeaa8a28d73bfcbf3b59/src/main/java/ru/dokwork/todo/TaskService.java?embed=t"></script>
</div>
<br />
Здесь вы снова не найдете поддержки метода PATCH из коробки, и снова добавить ее самостоятельно не составляет труда:<br />
<div class="code">
<pre><code class="java">
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("PATCH")
public @interface PATCH {
}
</code></pre>
</div>
<br />
Полный код рассмотренного в статье примера вы можете посмотреть здесь: <a href="http://bitbucket.org/dok/todo/">http://bitbucket.org/dok/todo/</a><br />
<h3 style="text-align: left;">
Заключение</h3>
</div>
<div>
Обзор получился несколько скомканным, но если отвлечься от примеров кода (недаром они спрятаны поумолчанию), то понять общую концепцию реализации RESTful приложений на Java должно быть не трудно. Благодаря JAX-RS вам не придется заботиться о нудной работе с сервлетами, вы даже можете не замечать того, что пишете веб приложение. Всего десяток аннотаций помогут превратить ваши POJO классы в полноценные REST сервисы!<br />
<br />
<br />
<i>За рамками данной статьи осталась еще одна очень важная тема: реализация аутентификации и авторизации в REST приложениях. Надеюсь я найду время чтобы изучить и осветить этот вопрос в ближайших статьях.</i></div>
</div>Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com5tag:blogger.com,1999:blog-7407396207758383724.post-82555532977153805722014-05-28T11:21:00.001+04:002014-07-30T18:04:12.729+04:00Как получить прямую ссылку на файл в Google Диск<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiqd80BrvRpJB_16A97FD03d_Dc4JVBY66QsWhpTOWQKi4o8QotqYnocn0cKtKzs5W6OD2jmnj4nZwnb_p7N3lMmS5wbOXZhYg4Z6WNpUwteVqOX55ZkFotVWs0JhTsWcLw1PZ_2y1Am4J/s1600/slide1.png" height="139" width="200" /></td><td>Часто возникает необходимость расшарить файл по прямой ссылке, например опубликовать фото или выложить css и js файлы для своего сайта. Пожалуй самый удобный из широко доступных способов сделать это - выложить файл в специальную папку Public в Dropbox. Но к сожалению возможность получения прямых ссылок на dropbox есть только у аккаунтов созданных до лета 2012 года. Как быть ели у вас такого аккаунта нет, да и dropbox вы не используете? Предлагаемый в статье вариант - использовать Google Диск. Да, да! В Google Диск <b>есть</b> возможность получения прямой ссылки!</td></tr>
</tbody></table>
<a name='more'></a><script type="text/javascript">
$(function() {
$("#_label").click(function () {
$("#_div").toggle();
});
});
</script><b>
Шаг 1. Понимаем как.</b><br />
<br />
Сама идея полностью позаимствована отсюда: <a href="http://www.magentawave.com/2013/09/get-direct-link-on-file.html">http://www.magentawave.com/2013/09/get-direct-link-on-file.html</a>. Там все подробно расписано и рассказано. Повторяться не буду.<br />
<b><br /></b>
<b>Шаг 2. Пишем скрипт.</b><br />
<b><br /></b>
Предложенный в оригинальной статье ресурс <a href="http://gdurl.com/">http://gdurl.com/</a> меня не устроил - не хочется зависеть от стороннего ресурса, в любой момент способного прекратить свое существование. Для таких же параноиков как и я, предлагаю консольное решение в виде следующего алиаса:<br />
<div class="console">
alias gdurl='sed "s,file/d/,uc\?export=download\&confirm=no_antivirus\&id=,g" | sed "s,/edit?usp=sharing,,g" | xclip -sel clip | echo "short url copied to clipboard"'</div>
<br />
<b>Шаг 3. Используем.
</b><br />
<br />
Копируем ссылку на расшариваемый файл:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELrLPJP8Z0FHC9v9exRIt9iRlZ6c2p7KAjzhihWWI_i_8yQS1xNKRPC9O4Onei6yPfT0nclNL0i8fCAU7s9daCgPD-uQXHge_0GnwUt89aDFLtXjFEWmWX4Cm8_mxy7A7-hCkrsJKdkpT/s1600/sharing+file.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjELrLPJP8Z0FHC9v9exRIt9iRlZ6c2p7KAjzhihWWI_i_8yQS1xNKRPC9O4Onei6yPfT0nclNL0i8fCAU7s9daCgPD-uQXHge_0GnwUt89aDFLtXjFEWmWX4Cm8_mxy7A7-hCkrsJKdkpT/s1600/sharing+file.png" height="340" width="640" /></a></div>
<br />
И преобразуем ее с помощью пары команд:<br />
<div class="console">
<pre>$ echo 'https://drive.google.com/file/d/0B0BL2IgYpiQ7N2VET09NX0haaXc/edit?usp=sharing' | gdurl
short url copied to clipboard</pre>
</div>
Все. Прямой url у теперь у нас в буфере обмена и готов для вставки!<br />
<br />
<a href="https://drive.google.com/uc?export=download&confirm=no_antivirus&id=0B0BL2IgYpiQ7N2VET09NX0haaXc">https://drive.google.com/uc?export=download&confirm=no_antivirus&id=0B0BL2IgYpiQ7N2VET09NX0haaXc</a><br />
<div>
<br /></div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com5tag:blogger.com,1999:blog-7407396207758383724.post-50662511304403774472014-03-28T10:35:00.000+04:002016-04-10T18:47:52.921+03:00Gradle to Maven<div dir="ltr" style="text-align: left;" trbidi="on">
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhutvM_uYp-DBMYnVlQfixQE_yT_jW0-xTb3yYaco2GnePbhnbCWnvcG4bMvyUAmupOkvFAQnYLxm4e1hbAdaic24imufQfb5JyoZdZ84HAd-bRMl8DaMGnu0F5_HePjxJef6cymUlhm9hN/s1600/gradle2maven.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhutvM_uYp-DBMYnVlQfixQE_yT_jW0-xTb3yYaco2GnePbhnbCWnvcG4bMvyUAmupOkvFAQnYLxm4e1hbAdaic24imufQfb5JyoZdZ84HAd-bRMl8DaMGnu0F5_HePjxJef6cymUlhm9hN/s1600/gradle2maven.png" height="171" width="200" /></a></div>
<br /></td><td>Столкнулся с достаточно редким случаем, когда потребовалось переехать с gradle обратно на maven. Самой неприятной рутиной задачей был перевод описаний зависимостей из однострочного gradle-представления в xml-представление maven.<br />
Представляю свое unix-way решение проблемы. Решение грубое, но может кому сгодится и сэкономит немного времени :)
<br />
<div class="console">
~$ cat build.gradle | grep compile | sed "s/\(compile '\)\(.*\)\(:\)\(.*\)\(:\)\(.*\)\('\)/<dependency><groupId>\2<\/groupId><artifactId>\4<\/artifactId><version>\6<\/version><\/dependency>/g;"</div>
Под катом небольшое пояснение.</td></tr>
</tbody></table>
<a name='more'></a><h3 style="text-align: left;">
Как это работает?</h3>
Берем build.gradle например такого содержания:<br />
<div class="code">
apply plugin: 'java'<br />
<br />
repositories {<br />
mavenCentral()<br />
mavenLocal()<br />
}<br />
<br />
dependencies {<br />
compile 'org.slf4j:slf4j-api:1.7.6'<br />
compile 'org.slf4j:slf4j-log4j:1.7.6'<br />
<br />
compile 'org.hibernate:hibernate-core:4.3.4.Final'<br />
compile 'org.hibernate:hibernate-entitymanager:4.3.4.Final'<br />
<br />
compile 'org.jboss.resteasy:jaxrs-api:3.0.6.Final'<br />
compile 'com.google.code.gson:gson:2.2.4'<br />
}</div>
Выводим его содержимое:<br />
<div class="console">
~$ cat ~/build.gradle</div>
Находим строки с compile:<br />
<div class="console">
~$ grep compile</div>
Изменяем их через регулярное выражение:<br />
<div class="console">
~$ sed "s/\(compile '\)\(.*\)\(:\)\(.*\)\(:\)\(.*\)\('\)/<dependency><groupId>\2<\/groupId><artifactId>\4<\/artifactId><version>\6<\/version><\/dependency>/g;"</div>
Объединяем все в цепочку вызовов через pipe:<br />
<div class="console">
~$ cat build.gradle | grep compile | sed "s/\(compile '\)\(.*\)\(:\)\(.*\)\(:\)\(.*\)\('\)/<dependency><groupId>\2<\/groupId><artifactId>\4<\/artifactId><version>\6<\/version><\/dependency>/g;"</div>
Получаем результат:<br />
<div class="code">
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.6</version></dependency><br />
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j</artifactId><version>1.7.6</version></dependency><br />
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-core</artifactId><version>4.3.4.Final</version></dependency><br />
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-entitymanager</artifactId><version>4.3.4.Final</version></dependency><br />
<dependency><groupId>org.jboss.resteasy</groupId><artifactId>jaxrs-api</artifactId><version>3.0.6.Final</version></dependency><br />
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.2.4</version></dependency></div>
<br />
<br /></div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0tag:blogger.com,1999:blog-7407396207758383724.post-88992539324758686212014-03-10T13:18:00.000+04:002014-03-12T17:45:47.351+04:00Java. Реализация шаблона DAO. Продолжение<div dir="ltr" style="text-align: left;" trbidi="on">
<style>
.pointer {
cursor: pointer;
color: #047;
}
.pointer:hover {
color: #c00;
}
</style>
<br />
<table><tbody>
<tr valign="top"><td><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRvOVoW80RG58eTMEGPShsaRlwW0DKLXVmFiEaMiDekzBcdpu8H8NhzHuKIFx2x77WoZFI_s254nD48OgxcEokd2JnAYXpuD1eI6r3erA6mt0vH6VgHooGkrB2zO71X9zBrUTR4wZlBx_B/s1600/diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRvOVoW80RG58eTMEGPShsaRlwW0DKLXVmFiEaMiDekzBcdpu8H8NhzHuKIFx2x77WoZFI_s254nD48OgxcEokd2JnAYXpuD1eI6r3erA6mt0vH6VgHooGkrB2zO71X9zBrUTR4wZlBx_B/s1600/diagram.png" height="135" width="320" /></a></div>
</td><td>В <a href="http://www.dokwork.ru/2014/02/daotalk.html" target="_blank">прошлой статье</a> мы начали разговор о реализации паттерна DAO в Java. В качестве наглядного примера использовалась ситуация с двумя сущностями: группой и студентом. В самом начале нашей беседы мы столкнулись с дилеммой: как реализовывать связь студент-группа? Сохранять ли первичный ключ группы, в которой состоит студент, или хранить объект, эту группу описывающий?<br />
Первый вариант мы уже рассмотрели. Пришла пора реализовать второй.</td></tr>
</tbody></table>
<a name='more'></a><h3 style="text-align: left;">
Второй способ. Храним ссылку на объект.</h3>
И так, наша цель абстрагироваться от реляционного представления и максимально обобщить код получившегося решения. Мы будем рассматривать простейшую связь многие-к-одному. В выбранном примере это означает, что в одной группе может учиться несколько студентов, но каждый студент может быть зачислен только в одну группу. Кроме того, у объектов описывающих студентов будет ссылка на группу, в то время как у объектов групп ссылки на студентов не будет.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhboFCj0_yQUvgl4cfBUl3yWKQyPyzs7As3QlVSj06T4-CcuyfJ6C90_GPRg5DvI1hIU0DMiqK4X0mQzd6IZE1EVuO_Js4OX3wbFDKFWWyEyuSFqoHr1ZHp81as06Fo-6ujPG5etVkjJRrQ/s1600/Student_and_Group.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhboFCj0_yQUvgl4cfBUl3yWKQyPyzs7As3QlVSj06T4-CcuyfJ6C90_GPRg5DvI1hIU0DMiqK4X0mQzd6IZE1EVuO_Js4OX3wbFDKFWWyEyuSFqoHr1ZHp81as06Fo-6ujPG5etVkjJRrQ/s1600/Student_and_Group.png" height="104" width="320" /></a></div>
<br />
<div class="pointer" id="student2_label" onclick="$('#student2_div').toggle();">
Student.java</div>
<div id="student2_div" style="display: none;">
<script src="https://bitbucket.org/dok/daotalk/src/fc1552e537b263edc68b1db114e3feef25d48c39/src/main/java/ru/dokwork/daotalk/domain/Student.java?embed=t"></script>
</div>
<h3 style="text-align: left;">
Что мы хотим получить? TDD</h3>
Ситуация, когда один хранимый объект имеет ссылку на другой хранимый объект, не столь прозрачна, как в случае с непосредственным отражением реляционной структуры. Прежде всего, давайте определимся с тем, что мы хотим получить, а точнее с тем, как должны вести себя наши объекты в CRUD(Create, Read, Update, Delete) ситуациях. В этом нам поможет принцип разработки отталкивающийся от тестов, так называемый<a href="http://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5" target="_blank"> <i style="background-color: white; font-family: sans-serif; font-size: 12.727272033691406px; line-height: 19.200000762939453px;"><span lang="en" xml:lang="en">test-driven development</span></i></a>. Вначале мы напишем тесты, которые описывают желаемое поведение, и в процессе статьи добьемся их выполнения.<br />
<h4 style="text-align: left;">
Create</h4>
Начнем с операции создания объекта. Прежде всего, объект должен создаваться без ошибок и допускать изменение ссылки на группу:<br />
<div class="code">
<pre><code class="java">
@Test
public void testCreate() throws PersistException {
Student student = (Student) factory.getDao(connection, Student.class).create();
Assert.assertNull("Group is not null.", student.getGroup());
Group group = new Group();
student.setGroup(group);
Assert.assertNotNull("Group is null.", student.getGroup());
}
</code></pre>
</div>
<br />
<h4 style="text-align: left;">
Persist</h4>
При сохранении объекта в базе, мы будем сохранять и его зависимости. Напомню, что при создании записи об объекте (метод persist), мы <a href="http://www.dokwork.ru/2014/02/daotalk.html#what_with_id" target="_blank">создаем новый объект</a>. Поэтому важно проконтролировать, что ссылка на группу не теряется. К тому же, здесь мы сталкиваемся с двоякой ситуацией: в одном случае объекту <span style="font-family: Courier New, Courier, monospace;">Group</span> уже может соответствовать запись в базе данных, в другом нет. Нам необходимо проверить оба варианта:
<br />
<div class="code">
<pre><code class="java">
@Test
public void testPersist() throws PersistException {
Student student = new Student();
Group group = (Group) factory.getDao(connection, Group.class).create();
student.setGroup(group);
group.setNumber(1234);
student = (Student) factory.getDao(connection, Student.class).persist(student);
Assert.assertNotNull("Group is null.", student.getGroup());
Assert.assertEquals("Wrong group number.", 1234, student.getGroup().getNumber());
}
@Test
public void testPersistAll() throws PersistException {
Student student = new Student();
student.setGroup(new Group());
student = (Student) factory.getDao(connection, Student.class).persist(student);
Assert.assertNotNull("Group is null.", student.getGroup());
Assert.assertNotNull("Group.id is null.", student.getGroup().getId());
}
</code></pre>
</div>
<br />
<h4 style="text-align: left;">
Update</h4>
При обновлении записи об объекте тесты практически идентичны тем, что мы будем использовать для проверки метода persist:
<br />
<div class="code">
<pre><code class="java">
@Test
public void testUpdate() throws PersistException {
Student student = (Student) factory.getDao(connection, Student.class).create();
student.setGroup(new Group());
factory.getDao(connection, Student.class).update(student);
Assert.assertNotNull("Group is null.", student.getGroup());
Assert.assertNotNull("Group.id is null.", student.getGroup().getId());
}
@Test
public void testUpdateAll() throws PersistException {
Student student = (Student) factory.getDao(connection, Student.class).create();
Group group = (Group) factory.getDao(connection, Group.class).create();
group.setNumber(1234);
student.setGroup(group);
factory.getDao(connection, Student.class).update(student);
Assert.assertNotNull("Group is null.", student.getGroup());
Assert.assertEquals("Wrong group number.", 1234, student.getGroup().getNumber());
}</code></pre>
</div>
<h4 style="text-align: left;">
Read</h4>
Пора позаботиться о том, чтобы объекты были доступны после записи их состояния в базу данных:
<br />
<div class="code">
<pre><code class="java">
@Test
public void testRead() throws PersistException {
Student student = (Student) factory.getDao(connection, Student.class).create();
student.setGroup(new Group());
factory.getDao(connection, Student.class).update(student);
student = (Student) factory.getDao(connection, Student.class).getByPK(student.getId());
Assert.assertNotNull("Student is null.", student);
Assert.assertNotNull("Group is null.", student.getGroup());
}</code></pre>
</div>
<br />
<h4 style="text-align: left;">
Delete</h4>
И наконец удаление. И вот здесь кроется один очень важный нюанс. Если удаление студента логически не ведет за собой удаление группы,
<br />
<div class="code">
<pre><code class="java">
@Test
public void testDelete() throws PersistException {
Student student = (Student) factory.getDao(connection, Student.class).create();
student.setGroup(new Group());
factory.getDao(connection, Student.class).update(student);
Group group = student.getGroup();
factory.getDao(connection, Student.class).delete(student);
group = (Group) factory.getDao(connection, Group.class).getByPK(group.getId());
Assert.assertNotNull("Group not found.", group);
}</code></pre>
</div>
то обратное, вообще говоря, не верно. В некоторых ситуациях удаление записи об объекте должно повлечь за собой и удаление записей всех ссылающихся на него объектов. И если на уровне базы данных нам не составит труда выполнить это, то удаление непосредственно ссылок на объекты (в нашем примере - студентов) оказывается не возможным (или очень трудно выполнимым). Поэтому ситуацию, когда удаление группы должно повлечь за собой удаление студентов, мы рассматривать не будем.
<br />
<div class="pointer" id="RelationTest_label" onclick="$('#RelationTest_div').toggle();">
RelationTest.java</div>
<div id="RelationTest_div" style="display: none;">
<script src="https://bitbucket.org/dok/daotalk/src/bbe5f2efb7863cc292d7d5e7e526f7ae2c15a3f3/src/test/java/ru/dokwork/daotalk/dao/RelationTest.java?embed=t"></script>
</div>
<h3 style="text-align: left;">
Приступим</h3>
<div>
<h4 style="text-align: left;">
MySqlStudentDao</h4>
Прежде всего нам необходимо исправить методы <span style="font-family: Courier New, Courier, monospace;">parseResultSet</span>, <span style="font-family: Courier New, Courier, monospace;">prepareStatementForInsert</span> и <span style="font-family: Courier New, Courier, monospace;">prepareStatementForUpdate</span> в классе <span style="font-family: Courier New, Courier, monospace;">MySqlStudentDao</span>. В методах <span style="font-family: Courier New, Courier, monospace;">prepareStatementForInsert</span> и <span style="font-family: Courier New, Courier, monospace;">prepareStatementForUpdate</span> изменится процесс получения идентификатора группы:<br />
<div class="code">
<pre><code class="java">
@Override
protected void prepareStatementForUpdate(PreparedStatement statement, Student object) throws PersistException {
try {
int groupId = (object.getGroup() == null || object.getGroup().getId() == null) ? -1
: object.getGroup().getId();
</code></pre>
<pre><code class="java"> ....</code></pre>
<pre><code class="java">
statement.setInt(4, groupId);
} catch (Exception e) {
throw new PersistException(e);
}
}
@Override
protected void prepareStatementForInsert(PreparedStatement statement, Student object) throws PersistException {
try {
int groupId = (object.getGroup() == null || object.getGroup().getId() == null) ? -1
: object.getGroup().getId();</code></pre>
<pre><code class="java">
.....
statement.setInt(4, groupId);
} catch (Exception e) {
throw new PersistException(e);
}
}</code></pre>
</div>
но с методом <span style="font-family: Courier New, Courier, monospace;">parseResultSet</span> все оказывается не так просто. Для его реализации мы должны получиться ссылку на объект группы, соответствующий идентификатору, полученному в результате выполнения запроса. Первое, что приходит в голову - сохранить ссылку на фабрику и обращаться за объектом к ней. Но это не лучшее решение, тк работа с фабрикой на прямую слишком сильно развязывает нам руки и может привести к выполнению не желательных действий.<br />
<br />
И все таки без ссылки на фабрику нам не обойтись. Мы сохраним ее, но сделаем это на уровне <span style="font-family: Courier New, Courier, monospace;">AbstractJDBCDao</span> в приватном поле, а для получения необходимого объекта объявим protected метод, возвращающий объект указываемого класса, по значению указанного ключа:<br />
<div class="code">
<pre><code class="java">protected Identified getDependence(Class<? extends Identified> dtoClass, Serializable pk);</code></pre>
</div>
в таком случае реализовать метод parseResultSet не составит труда:<br />
<div class="code">
<pre><code class="java">@Override
protected List<Student> parseResultSet(ResultSet rs) throws PersistException {
LinkedList<Student> result = new LinkedList<Student>();
try {
while (rs.next()) {
...
student.setGroup((Group)getDependence(Group.class, rs.getInt("group_id")));
...
}
} catch (Exception e) {
throw new PersistException(e);
}
return result;
}</code></pre>
</div>
<br />
<h4 style="text-align: left;">
AbstractJDBCDao</h4>
В нашем базовом дао-классе стало на одну зависимость больше. Теперь он требует при создании указывать создающую его фабрику (конструкторов наследующих классов это изменение тоже касается):
<br />
<div class="code">
<pre><code class="java">public abstract class AbstractJDBCDao<T extends Identified<PK>, PK extends Integer> implements GenericDao<T, PK> {
private DaoFactory<Connection> parentFactory;
...
public AbstractJDBCDao(DaoFactory<Connection> parentFactory, Connection connection) {
this.parentFactory = parentFactory;
this.connection = connection;
}
protected Identified getDependence(Class<? extends Identified> dtoClass, Serializable pk) throws PersistException {
return parentFactory.getDao(connection, dtoClass).getByPK(pk);
}
...
}
</code></pre>
</div>
<br />
А теперь самое интересное. Чтобы выполнить метод persist или update мы должны сохранить состояние всех объектов на которые ссылаемся. При чем сделать это надо до сохранения самого объекта, иначе может сложиться ситуация, когда зависимый объект окажется только созданным и не сохраненным в базу, тогда его идентификатор не будет определен, что не позволит правильно сделать запись об объекте, который на него ссылается:
<br />
<div class="code">
<pre><code class="java">
@Override
public T persist(T object) throws PersistException {
if (object.getId() != null) {
throw new PersistException("Object is already persist.");
}
// Сохраняем зависимости
saveDependences(object);
...
}
@Override
public void update(T object) throws PersistException {
// Сохраняем зависимости
saveDependences(object);
...
}
</code></pre>
</div>
<br />
Напомню, что методы <span style="font-family: Courier New, Courier, monospace;">persist</span> и <span style="font-family: Courier New, Courier, monospace;">update</span> реализованы в базовом абстрактном классе <span style="font-family: 'Courier New', Courier, monospace;">AbstractJDBCDao</span><span style="font-family: inherit;">,</span> который ничего не знает о полях объекта с зависимостями. И тем не менее, для реализации метода <span style="font-family: Courier New, Courier, monospace;">saveDependences</span> нам необходимо перебрать все такие поля. Каким же образом нам лучше всего получить список таких полей? И как отличить их от прочих? Для этого мы создадим новый класс - <span style="font-family: Courier New, Courier, monospace;">ManyToOne</span>, который будет отвечать за работу с полем, ссылающимся на хранимый объект. Интерфейс класса определит себя сам, в процессе реализации метода <span style="font-family: 'Courier New', Courier, monospace;">saveDependences</span>:<br />
<div class="code">
<pre><code class="java">private void saveDependences() throws PersistException {
for (ManyToOne m : relations) {
if (m.getDependence() == null) {
continue;
}
if (m.getDependence().getId() == null) {
Identified depend = m.persistDependence(connection);
m.setDependence(depend);
} else {
m.updateDependence(connection);
}
}
}</code></pre>
</div>
<h3 style="text-align: left;">
ManyToOne</h3>
Первая версия интерфейса класса ManyToOne:
<br />
<div class="code">
<pre><code class="java">/**
* Отвечает за реализацию связи многие-к-одному.
*
* @param <Owner> класс объекта, чье поле ссылается на зависимый объект.
* @param <Dependence> класс зависимого объекта.
*/
public class ManyToOne<Owner extends Identified, Dependence extends Identified> {
public Dependence getDependence() {
return null;
}
public void setDependence(Dependence dependence) {
}
public Dependence persistDependence(Connection connection) {
return null;
}
public void updateDependence(Connection connection) {
}
}</code></pre>
</div>
<br />
Для реализации такого интерфейса нам явно понадобятся дополнительные зависимости. В частности реализация методов <span style="font-family: Courier New, Courier, monospace;">getDependence</span> и <span style="font-family: Courier New, Courier, monospace;">setDependence</span> очевидно требуют ссылки на объект класса <span style="font-family: Courier New, Courier, monospace;">Owner</span> и дополнительной информации непосредственно о том поле, которое ссылается на зависимый объект. Чтобы реализовать методы <span style="font-family: Courier New, Courier, monospace;">persistDependence</span> и <span style="font-family: Courier New, Courier, monospace;">updateDependence</span> необходима ссылка на фабрику дао-объектов и класс зависимого объекта:
<br />
<div class="code">
<pre><code class="java">/**
* Отвечает за реализацию связи многие-к-одному.
*
* @param <Owner> класс объекта, чье поле ссылается на зависимый объект.
* @param <Dependence> класс зависимого объекта.
*/
public class ManyToOne<Owner extends Identified, Dependence extends Identified> {
private DaoFactory<Connection> factory;
private Field field;
private String name;
private int hash;
public Dependence getDependence(Owner owner) throws IllegalAccessException {
return (Dependence) field.get(owner);
}
public void setDependence(Owner owner, Dependence dependence) throws IllegalAccessException {
field.set(owner, dependence);
}
public Identified persistDependence(Owner owner, Connection connection) throws IllegalAccessException, PersistException {
return factory.getDao(connection, field.getType()).persist(getDependence(owner));
}
public void updateDependence(Owner owner, Connection connection) throws IllegalAccessException, PersistException {
factory.getDao(connection, field.getType()).update(getDependence(owner));
}
@Override
public String toString() {
return name;
}
@Override
public int hashCode() {
return hash;
}
public ManyToOne(Class<Owner> ownerClass, DaoFactory<Connection> factory, String field) throws NoSuchFieldException {
this.factory = factory;
this.field = ownerClass.getDeclaredField(field);
this.field.setAccessible(true);
name = ownerClass.getSimpleName() + " to " + this.field.getType().getSimpleName();
hash = ownerClass.hashCode() & field.hashCode();
}
}</code></pre>
</div>
Мы близки к успеху! Осталось адаптировать метод <span style="font-family: Courier New, Courier, monospace;">saveDependences</span> класса <span style="font-family: Courier New, Courier, monospace;">AbstractJDBCDao</span> к новому интерфейсу <span style="font-family: Courier New, Courier, monospace;">ManyToOne</span> и реализовать добавление новой связи в набор <span style="font-family: Courier New, Courier, monospace;">relations</span>. В классе <span style="font-family: Courier New, Courier, monospace;">ManyToOne</span> я заранее переопределил методы <span style="font-family: Courier New, Courier, monospace;">hashCode</span> и <span style="font-family: Courier New, Courier, monospace;">toString</span> чтобы хранить связи в <span style="font-family: Courier New, Courier, monospace;">HashSet</span> и подробнее комментировать возможные ошибки:<br />
<div class="code">
<pre><code class="java">
...
private Set<ManyToOne> relations = new HashSet<ManyToOne>();
...
protected boolean addRelation(Class<? extends Identified> ownerClass, String field) {
try {
return relations.add(new ManyToOne(ownerClass, parentFactory, field));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private void saveDependences(Identified owner) throws PersistException {
for (ManyToOne m : relations) {
try {
if (m.getDependence(owner) == null) {
continue;
}
if (m.getDependence(owner).getId() == null) {
Identified depend = m.persistDependence(owner, connection);
m.setDependence(owner, depend);
} else {
m.updateDependence(owner, connection);
}
} catch (Exception e) {
throw new PersistException("Exception on save dependence in relation " + m + ".", e);
}
}
}
</code></pre>
</div>
<br />
Регистрация связей должна происходить в конструкторе дао-объектов:
<br />
<div class="code">
<pre><code class="java">
...
public MySqlStudentDao(DaoFactory<Connection> parentFactory, Connection connection) {
super(parentFactory, connection);
addRelation(Student.class, "group");
}
...
</code></pre>
</div>
<br />
Вот и все. Исправив мелкие ошибки компиляции, связанные с изменение интерфейсов используемых ранее классов, мы можем запускать наши тесты и радоваться их успешному выполнению!<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSl6HcVd5COBnBBCmtoxt8YuDTNLuwmKT8aEIBxlyEXPsuFDWr-hKXLPee9wYM572qDeR6TM29gqY6WRxXytILERzVsLLlW-hXyghckD8FxHMSjqLBNxxauyftS4axRmrjrE9qInz5AMvf/s1600/tests.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSl6HcVd5COBnBBCmtoxt8YuDTNLuwmKT8aEIBxlyEXPsuFDWr-hKXLPee9wYM572qDeR6TM29gqY6WRxXytILERzVsLLlW-hXyghckD8FxHMSjqLBNxxauyftS4axRmrjrE9qInz5AMvf/s1600/tests.png" height="104" width="640" /></a></div>
Исходный код можно получить <a href="https://bitbucket.org/dok/daotalk" target="_blank">здесь</a> под тегом 2.0.<br />
<h3 style="text-align: left;">
Подводя итоги</h3>
<div>
Использование JDBC для реализации DAO дает наиболее полное понимание того, как именно происходит взаимодействие с базой данных. Позволяет выполнять это взаимодействие максимально гибко и оптимально. </div>
<div>
<br /></div>
<div>
Но давайте говорить честно, простое решение, представленное в предыдущей статье, мало пригодно для жизни, т.к. редко встречается ситуация, когда сущности не имеют связей, а при наличии этих связей идея хранения в полях объектов внешних ключей, приводит к необходимости нетривиального жонглирования сущностями и dao объектами. </div>
<div>
<br /></div>
<div>
Идея возвращать в полях ссылки на зависимости выглядит более привлекательной для использования, но требует достаточно трудного решения. Мы рассмотрели только одну связь и при том не самую сложную, да еще и с упрощением (допустили существование каждой сущности раздельно), но код все равно получился большим и сложным.</div>
<div>
<br /></div>
<div>
Вынесение кода в AbstractJDBCDao позволило серьезно упростить реализацию дао-классов для новых сущностей, но эта задача так и осталась крайне ресурсоемкой.</div>
<div>
<br /></div>
Все это приводит к необходимости поиска альтернативных решений, которые могли бы избавить нас от большинства рутиной работы. Следующим шагом на этом пути должна стать попытка использования <a href="http://ru.wikipedia.org/wiki/ORM" target="_blank">ORM</a> решений, таких как <a href="http://ru.wikipedia.org/wiki/Hibernate_(%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0)" target="_blank">Hibernate</a> или <a href="http://mybatis.github.io/mybatis-3/" target="_blank">MyBatis</a>.<br />
<br />
<br />
<br />
<br />
<br /></div>
</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com15tag:blogger.com,1999:blog-7407396207758383724.post-53658773170414139802014-03-01T13:36:00.001+04:002014-03-01T13:36:33.711+04:00Gradle в Ubuntu. JAVA_HOME is set to an invalid directory<div dir="ltr" style="text-align: left;" trbidi="on">
После установки gradle в Ubuntu столкнулся со странной проблемой: gradle рьяно утверждал, что <span style="font-family: Courier New, Courier, monospace;">JAVA_HOME</span> не корректна и якобы ссылается на <span style="font-family: Courier New, Courier, monospace;">/usr/lib/jvm/default-java</span>.
<br />
<div class="console">
<pre>$ gradle
ERROR: JAVA_HOME is set to an invalid directory:
/usr/lib/jvm/default-java
Please set the JAVA_HOME variable in your
environment to match the location of your
Java installation.
</pre>
</div>
При этом, это было наглой ложью с его стороны, т.к. эта переменная имела иное значение и была абсолютно корректной:<br />
<div class="console">
<pre>$ echo $JAVA_HOME
/usr/lib/jvm/java-7-oracle
</pre>
</div>
<br />
<a name='more'></a><br />
Вот грубое решение данной проблемы:<br />
<ol style="text-align: left;">
<li>Открываем для редактирования под рутом файл<span style="font-family: Courier New, Courier, monospace;"> /usr/bin/gradle</span></li>
<li>Находим строчку <span style="font-family: Courier New, Courier, monospace;">export JAVA_HOME=/usr/lib/jvm/default-java</span></li>
<li>И закомментируем ее: <span style="font-family: Courier New, Courier, monospace;">#export JAVA_HOME=/usr/lib/jvm/default-java</span></li>
<li>Proffit!</li>
</ol>
<div class="console">
<pre>$ gradle
:help
Welcome to Gradle 1.4.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see a list of command-line options, run gradle --help
BUILD SUCCESSFUL
Total time: 2.877 secs
</pre>
</div>
Ссылка на гредловый баг трекер с соответствующей проблемой: <a href="http://issues.gradle.org/browse/GRADLE-2545">http://issues.gradle.org/browse/GRADLE-2545</a><br />
<br />
Кстати, как видите версия gradle в репозитории Ubuntu 12.04 крайне не свежая. В последних версиях дистрибутивов gradle эта проблема, как и проблемная строка в конфигурации, не встречается.</div>
Vladimir Popovhttp://www.blogger.com/profile/10396640252599430444noreply@blogger.com0